From 220a9874679be2dd50adee0f6cda6f189a526023 Mon Sep 17 00:00:00 2001 From: donandren Date: Sat, 14 Jan 2017 16:24:58 +0200 Subject: [PATCH 001/482] issue #855 unit test --- .../ListBoxTests_Single.cs | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index c7992fe80f..d95acbdb7f 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -1,11 +1,15 @@ // 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.ComponentModel; using System.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Input; using Avalonia.LogicalTree; +using Avalonia.Markup.Xaml.Data; using Avalonia.Styling; using Avalonia.VisualTree; using Xunit; @@ -199,6 +203,71 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, target.SelectedIndex); } + [Fact] + public void SelectedItem_Should_Not_Cause_StackOverflow() + { + var viewModel = new TestStackOverflowViewModel() + { + Items = new List { "foo", "bar", "baz" } + }; + + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + DataContext = viewModel, + Items = viewModel.Items + }; + + target.Bind(ListBox.SelectedItemProperty, + new Binding("SelectedItem") { Mode = BindingMode.TwoWay }); + + Assert.Equal(0, viewModel.SetterInvokedCount); + + //here in real life stack overflow exception is thrown issue #855 + target.SelectedItem = viewModel.Items[2]; + + Assert.Equal(viewModel.Items[1], target.SelectedItem); + Assert.Equal(1, viewModel.SetterInvokedCount); + } + + private class TestStackOverflowViewModel : INotifyPropertyChanged + { + public List Items { get; set; } + + public int SetterInvokedCount { get; private set; } + + public const int MaxInvokedCount = 1000; + + private string _selectedItem; + + public event PropertyChangedEventHandler PropertyChanged; + + public string SelectedItem + { + get { return _selectedItem; } + set + { + if (_selectedItem != value) + { + SetterInvokedCount++; + + int index = Items.IndexOf(value); + + if (MaxInvokedCount > SetterInvokedCount && index > 0) + { + _selectedItem = Items[index - 1]; + } + else + { + _selectedItem = value; + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem))); + } + } + } + } + private Control CreateListBoxTemplate(ITemplatedControl parent) { return new ScrollViewer @@ -237,4 +306,4 @@ namespace Avalonia.Controls.UnitTests target.Presenter.ApplyTemplate(); } } -} +} \ No newline at end of file From 3729b9797fbae73bd1b0f30204d3d01d8c8493b4 Mon Sep 17 00:00:00 2001 From: donandren Date: Sun, 15 Jan 2017 16:06:43 +0200 Subject: [PATCH 002/482] another failing test for issue #855 and #824 --- .../Primitives/RangeBaseTests.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs index d3ed077cbf..9bccb1986d 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs @@ -2,7 +2,11 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.ComponentModel; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Markup.Xaml.Data; using Xunit; namespace Avalonia.Controls.UnitTests.Primitives @@ -87,8 +91,93 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.Throws(() => target.Value = double.NegativeInfinity); } + [Fact] + public void SetValue_Should_Not_Cause_StackOverflow() + { + var viewModel = new TestStackOverflowViewModel() + { + Value = 50 + }; + + Track track = null; + + var target = new TestRange() + { + Template = new FuncControlTemplate(c => + { + return track = new Track() + { + Width = 100, + Orientation = Orientation.Horizontal, + [~~Track.MinimumProperty] = c[~~RangeBase.MinimumProperty], + [~~Track.MaximumProperty] = c[~~RangeBase.MaximumProperty], + [~~Track.ValueProperty] = c[~~RangeBase.ValueProperty], + Name = "PART_Track", + Thumb = new Thumb() + }; + }), + Minimum = 0, + Maximum = 100, + DataContext = viewModel + }; + + target.Bind(TestRange.ValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay }); + + target.ApplyTemplate(); + track.Measure(new Size(100, 0)); + track.Arrange(new Rect(0, 0, 100, 0)); + + Assert.Equal(1, viewModel.SetterInvokedCount); + + //here in real life stack overflow exception is thrown issue #855 and #824 + target.Value = 51.001; + + Assert.Equal(2, viewModel.SetterInvokedCount); + + double expected = 51; + + Assert.Equal(expected, viewModel.Value); + Assert.Equal(expected, target.Value); + Assert.Equal(expected, track.Value); + } + private class TestRange : RangeBase { } + + private class TestStackOverflowViewModel : INotifyPropertyChanged + { + public int SetterInvokedCount { get; private set; } + + public const int MaxInvokedCount = 1000; + + private double _value; + + public event PropertyChangedEventHandler PropertyChanged; + + public double Value + { + get { return _value; } + set + { + if (_value != value) + { + SetterInvokedCount++; + if (SetterInvokedCount < MaxInvokedCount) + { + _value = (int)value; + if (_value > 75) _value = 75; + if (_value < 25) _value = 25; + } + else + { + _value = value; + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); + } + } + } + } } } \ No newline at end of file From ef81c5596051ac123a46c9165b455c48d3bcb06d Mon Sep 17 00:00:00 2001 From: donandren Date: Fri, 17 Feb 2017 00:32:38 +0200 Subject: [PATCH 003/482] another simple unit tests for issue #855 for direct and styled properties --- .../Avalonia.Base.UnitTests.csproj | 4 + .../AvaloniaObjectTests_Binding.cs | 103 +++++++++++++++-- .../AvaloniaObjectTests_Direct.cs | 106 ++++++++++++++++-- 3 files changed, 191 insertions(+), 22 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj index 07ed7f14ca..c712a8bcf9 100644 --- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj +++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj @@ -124,6 +124,10 @@ {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Avalonia.Base + + {3E53A01A-B331-47F3-B828-4A5717E77A24} + Avalonia.Markup.Xaml + {88060192-33d5-4932-b0f9-8bd2763e857d} Avalonia.UnitTests diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 5e286305d2..7186207b92 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -2,22 +2,21 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections; -using System.Collections.Generic; +using System.ComponentModel; +using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Subjects; -using Microsoft.Reactive.Testing; +using System.Threading; +using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Logging; -using Avalonia.UnitTests; -using Xunit; -using System.Threading.Tasks; +using Avalonia.Markup.Xaml.Data; using Avalonia.Platform; -using System.Threading; -using Moq; -using System.Reactive.Disposables; -using System.Reactive.Concurrency; using Avalonia.Threading; +using Avalonia.UnitTests; +using Microsoft.Reactive.Testing; +using Moq; +using Xunit; namespace Avalonia.Base.UnitTests { @@ -363,7 +362,7 @@ namespace Avalonia.Base.UnitTests Assert.True(called); } } - + [Fact] public async void Bind_With_Scheduler_Executes_On_Scheduler() { @@ -384,7 +383,43 @@ namespace Avalonia.Base.UnitTests await Task.Run(() => source.OnNext(6.7)); } + } + + [Fact] + public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() + { + var viewModel = new TestStackOverflowViewModel() + { + Value = 50 + }; + + var target = new Class1(); + + //note: if the initialization of the child binding is here target/child binding work fine!!! + //var child = new Class1() + //{ + // [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty] + //}; + target.Bind(Class1.DoubleValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel }); + + var child = new Class1() + { + [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty] + }; + + Assert.Equal(1, viewModel.SetterInvokedCount); + + //here in real life stack overflow exception is thrown issue #855 and #824 + target.DoubleValue = 51.001; + + Assert.Equal(2, viewModel.SetterInvokedCount); + + double expected = 51; + + Assert.Equal(expected, viewModel.Value); + Assert.Equal(expected, target.DoubleValue); + Assert.Equal(expected, child.DoubleValue); } /// @@ -405,6 +440,15 @@ namespace Avalonia.Base.UnitTests public static readonly StyledProperty QuxProperty = AvaloniaProperty.Register("Qux", 5.6); + + public static readonly StyledProperty DoubleValueProperty = + AvaloniaProperty.Register(nameof(DoubleValue)); + + public double DoubleValue + { + get { return GetValue(DoubleValueProperty); } + set { SetValue(DoubleValueProperty, value); } + } } private class Class2 : Class1 @@ -431,5 +475,40 @@ namespace Avalonia.Base.UnitTests return new InstancedBinding(_source, BindingMode.OneTime); } } + + private class TestStackOverflowViewModel : INotifyPropertyChanged + { + public int SetterInvokedCount { get; private set; } + + public const int MaxInvokedCount = 1000; + + private double _value; + + public event PropertyChangedEventHandler PropertyChanged; + + public double Value + { + get { return _value; } + set + { + if (_value != value) + { + SetterInvokedCount++; + if (SetterInvokedCount < MaxInvokedCount) + { + _value = (int)value; + if (_value > 75) _value = 75; + if (_value < 25) _value = 25; + } + else + { + _value = value; + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); + } + } + } + } } -} +} \ No newline at end of file diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index ecb555252d..b6e5396020 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -3,10 +3,11 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Reactive.Subjects; -using Avalonia; using Avalonia.Data; using Avalonia.Logging; +using Avalonia.Markup.Xaml.Data; using Avalonia.UnitTests; using Xunit; @@ -208,7 +209,7 @@ namespace Avalonia.Base.UnitTests { var target = new Class1(); - Assert.Throws(() => + Assert.Throws(() => target.SetValue(Class1.BarProperty, "newvalue")); } @@ -217,7 +218,7 @@ namespace Avalonia.Base.UnitTests { var target = new Class1(); - Assert.Throws(() => + Assert.Throws(() => target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue")); } @@ -227,7 +228,7 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); var source = new Subject(); - Assert.Throws(() => + Assert.Throws(() => target.Bind(Class1.BarProperty, source)); } @@ -439,12 +440,49 @@ namespace Avalonia.Base.UnitTests Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata().DefaultBindingMode); } + [Fact] + public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() + { + var viewModel = new TestStackOverflowViewModel() + { + Value = 50 + }; + + var target = new Class1(); + + //note: if the initialization of the child binding is here there is no stackoverflow!!! + //var child = new Class1() + //{ + // [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty] + //}; + + target.Bind(Class1.DoubleValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel }); + + var child = new Class1() + { + [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty] + }; + + Assert.Equal(1, viewModel.SetterInvokedCount); + + //here in real life stack overflow exception is thrown issue #855 and #824 + target.DoubleValue = 51.001; + + Assert.Equal(2, viewModel.SetterInvokedCount); + + double expected = 51; + + Assert.Equal(expected, viewModel.Value); + Assert.Equal(expected, target.DoubleValue); + Assert.Equal(expected, child.DoubleValue); + } + private class Class1 : AvaloniaObject { public static readonly DirectProperty FooProperty = AvaloniaProperty.RegisterDirect( - "Foo", - o => o.Foo, + "Foo", + o => o.Foo, (o, v) => o.Foo = v, unsetValue: "unset"); @@ -453,14 +491,21 @@ namespace Avalonia.Base.UnitTests public static readonly DirectProperty BazProperty = AvaloniaProperty.RegisterDirect( - "Bar", - o => o.Baz, - (o,v) => o.Baz = v, + "Bar", + o => o.Baz, + (o, v) => o.Baz = v, unsetValue: -1); + public static readonly DirectProperty DoubleValueProperty = + AvaloniaProperty.RegisterDirect( + nameof(DoubleValue), + o => o.DoubleValue, + (o, v) => o.DoubleValue = v); + private string _foo = "initial"; private readonly string _bar = "bar"; private int _baz = 5; + private double _doubleValue; public string Foo { @@ -478,6 +523,12 @@ namespace Avalonia.Base.UnitTests get { return _baz; } set { SetAndRaise(BazProperty, ref _baz, value); } } + + public double DoubleValue + { + get { return _doubleValue; } + set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); } + } } private class Class2 : AvaloniaObject @@ -497,5 +548,40 @@ namespace Avalonia.Base.UnitTests set { SetAndRaise(FooProperty, ref _foo, value); } } } + + private class TestStackOverflowViewModel : INotifyPropertyChanged + { + public int SetterInvokedCount { get; private set; } + + public const int MaxInvokedCount = 1000; + + private double _value; + + public event PropertyChangedEventHandler PropertyChanged; + + public double Value + { + get { return _value; } + set + { + if (_value != value) + { + SetterInvokedCount++; + if (SetterInvokedCount < MaxInvokedCount) + { + _value = (int)value; + if (_value > 75) _value = 75; + if (_value < 25) _value = 25; + } + else + { + _value = value; + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); + } + } + } + } } -} +} \ No newline at end of file From 8f3ce463f0c1e97642b43d8727525a6f95fcab40 Mon Sep 17 00:00:00 2001 From: donandren Date: Sun, 26 Feb 2017 21:27:44 +0200 Subject: [PATCH 004/482] Slider (RangeBase) test for #824 with Binding which behind scenes is using weakobservable --- .../Primitives/RangeBaseTests.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs index 9bccb1986d..0e67581760 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs @@ -7,6 +7,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Markup.Xaml.Data; +using Avalonia.Styling; using Xunit; namespace Avalonia.Controls.UnitTests.Primitives @@ -91,8 +92,10 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.Throws(() => target.Value = double.NegativeInfinity); } - [Fact] - public void SetValue_Should_Not_Cause_StackOverflow() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SetValue_Should_Not_Cause_StackOverflow(bool useXamlBinding) { var viewModel = new TestStackOverflowViewModel() { @@ -105,16 +108,32 @@ namespace Avalonia.Controls.UnitTests.Primitives { Template = new FuncControlTemplate(c => { - return track = new Track() + track = new Track() { Width = 100, Orientation = Orientation.Horizontal, [~~Track.MinimumProperty] = c[~~RangeBase.MinimumProperty], [~~Track.MaximumProperty] = c[~~RangeBase.MaximumProperty], - [~~Track.ValueProperty] = c[~~RangeBase.ValueProperty], + Name = "PART_Track", Thumb = new Thumb() }; + + if (useXamlBinding) + { + track.Bind(Track.ValueProperty, new Binding("Value") + { + Mode = BindingMode.TwoWay, + Source = c, + Priority = BindingPriority.Style + }); + } + else + { + track[~~Track.ValueProperty] = c[~~RangeBase.ValueProperty]; + } + + return track; }), Minimum = 0, Maximum = 100, From ce387271cc3f77a6ba622cf66402ef6f9c83de9c Mon Sep 17 00:00:00 2001 From: donandren Date: Sun, 26 Feb 2017 23:54:24 +0200 Subject: [PATCH 005/482] add test parameters to styled/direct binding tests for #855 and #824 --- .../AvaloniaObjectTests_Binding.cs | 26 +++++++++++++---- .../AvaloniaObjectTests_Direct.cs | 29 +++++++++++++++---- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 7186207b92..95aaa1fa32 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -385,8 +385,10 @@ namespace Avalonia.Base.UnitTests } } - [Fact] - public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values(bool useXamlBinding) { var viewModel = new TestStackOverflowViewModel() { @@ -401,12 +403,24 @@ namespace Avalonia.Base.UnitTests // [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty] //}; - target.Bind(Class1.DoubleValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel }); + target.Bind(Class1.DoubleValueProperty, + new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel }); + + var child = new Class1(); - var child = new Class1() + if (useXamlBinding) { - [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty] - }; + child.Bind(Class1.DoubleValueProperty, + new Binding("DoubleValue") + { + Mode = BindingMode.TwoWay, + Source = target + }); + } + else + { + child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty]; + } Assert.Equal(1, viewModel.SetterInvokedCount); diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index b6e5396020..17af8164b6 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -440,8 +440,10 @@ namespace Avalonia.Base.UnitTests Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata().DefaultBindingMode); } - [Fact] - public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values(bool useXamlBinding) { var viewModel = new TestStackOverflowViewModel() { @@ -456,12 +458,27 @@ namespace Avalonia.Base.UnitTests // [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty] //}; - target.Bind(Class1.DoubleValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel }); + target.Bind(Class1.DoubleValueProperty, new Binding("Value") + { + Mode = BindingMode.TwoWay, + Source = viewModel + }); + + var child = new Class1(); - var child = new Class1() + if (useXamlBinding) { - [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty] - }; + child.Bind(Class1.DoubleValueProperty, + new Binding("DoubleValue") + { + Mode = BindingMode.TwoWay, + Source = target + }); + } + else + { + child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty]; + } Assert.Equal(1, viewModel.SetterInvokedCount); From 1eee17345bf2958f0179794a4b21fb9d15469196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Thu, 6 Jul 2017 17:45:21 +0100 Subject: [PATCH 006/482] Implemented Window.WindowStartupLocation and WindowBase.Owner. --- src/Avalonia.Controls/Window.cs | 28 +++++++ src/Avalonia.Controls/WindowBase.cs | 15 ++++ .../WindowStartupLocation.cs | 23 ++++++ src/Avalonia.DotNetCoreRuntime/AppBuilder.cs | 3 + .../WindowTests.cs | 77 ++++++++++++++++++- 5 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 src/Avalonia.Controls/WindowStartupLocation.cs diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 3802f2b6ea..3535510ce3 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -183,6 +183,15 @@ namespace Avalonia.Controls set { SetValue(IconProperty, value); } } + /// + /// Gets or sets the startup location of the window. + /// + public WindowStartupLocation WindowStartupLocation + { + get; + set; + } + /// Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize; @@ -246,6 +255,7 @@ namespace Avalonia.Controls s_windows.Add(this); EnsureInitialized(); + SetWindowStartupLocation(); IsVisible = true; LayoutManager.Instance.ExecuteInitialLayoutPass(this); @@ -285,6 +295,7 @@ namespace Avalonia.Controls s_windows.Add(this); EnsureInitialized(); + SetWindowStartupLocation(); IsVisible = true; LayoutManager.Instance.ExecuteInitialLayoutPass(this); @@ -321,6 +332,23 @@ namespace Avalonia.Controls } } + void SetWindowStartupLocation() + { + if (WindowStartupLocation == WindowStartupLocation.CenterScreen) + { + var positionAsSize = PlatformImpl.MaxClientSize / 2 - ClientSize / 2; + Position = new Point(positionAsSize.Width, positionAsSize.Height); + } + else if (WindowStartupLocation == WindowStartupLocation.CenterOwner) + { + if (Owner != null) + { + var positionAsSize = Owner.ClientSize / 2 - ClientSize / 2; + Position = Owner.Position + new Point(positionAsSize.Width, positionAsSize.Height); + } + } + } + /// void INameScope.Register(string name, object element) { diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index fbdf64b14a..dd1e8fbef1 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -29,6 +29,12 @@ namespace Avalonia.Controls public static readonly DirectProperty IsActiveProperty = AvaloniaProperty.RegisterDirect(nameof(IsActive), o => o.IsActive); + /// + /// Defines the property. + /// + public static readonly StyledProperty OwnerProperty = + AvaloniaProperty.Register(nameof(Owner)); + private bool _hasExecutedInitialLayoutPass; private bool _isActive; private bool _ignoreVisibilityChange; @@ -100,6 +106,15 @@ namespace Avalonia.Controls private set; } + /// + /// Gets or sets the owner of the window. + /// + public WindowBase Owner + { + get { return GetValue(OwnerProperty); } + set { SetValue(OwnerProperty, value); } + } + /// /// Activates the window. /// diff --git a/src/Avalonia.Controls/WindowStartupLocation.cs b/src/Avalonia.Controls/WindowStartupLocation.cs new file mode 100644 index 0000000000..1818636076 --- /dev/null +++ b/src/Avalonia.Controls/WindowStartupLocation.cs @@ -0,0 +1,23 @@ +namespace Avalonia.Controls +{ + /// + /// Determines the startup location of the window. + /// + public enum WindowStartupLocation + { + /// + /// The startup location is defined by the Position property. + /// + Manual, + + /// + /// The startup location is the center of the screen. + /// + CenterScreen, + + /// + /// The startup location is the center of the owner window. If the owner window is not specified, the startup location will be . + /// + CenterOwner + } +} diff --git a/src/Avalonia.DotNetCoreRuntime/AppBuilder.cs b/src/Avalonia.DotNetCoreRuntime/AppBuilder.cs index 2b9b3083b1..bf8d7a20fd 100644 --- a/src/Avalonia.DotNetCoreRuntime/AppBuilder.cs +++ b/src/Avalonia.DotNetCoreRuntime/AppBuilder.cs @@ -10,6 +10,9 @@ using Avalonia.Shared.PlatformSupport; namespace Avalonia { + /// + /// Initializes platform-specific services for an . + /// public sealed class AppBuilder : AppBuilderBase { /// diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index e0dd908bbb..83f86ce5a0 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -68,7 +68,7 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void IsVisible_Should_Be_False_Atfer_Hide() + public void IsVisible_Should_Be_False_After_Hide() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -82,7 +82,7 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void IsVisible_Should_Be_False_Atfer_Close() + public void IsVisible_Should_Be_False_After_Close() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -96,7 +96,7 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void IsVisible_Should_Be_False_Atfer_Impl_Signals_Close() + public void IsVisible_Should_Be_False_After_Impl_Signals_Close() { var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); @@ -191,5 +191,76 @@ namespace Avalonia.Controls.UnitTests // AvaloniaLocator scopes. ((IList)Window.OpenWindows).Clear(); } + + [Fact] + public void Window_Should_Be_Centered_When_Window_Startup_Location_Is_Center_Screen() + { + var windowImpl = new Mock(); + windowImpl.SetupProperty(x => x.Position); + windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); + windowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1920, 1080)); + windowImpl.Setup(x => x.Scaling).Returns(1); + + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var window = new Window(); + window.WindowStartupLocation = WindowStartupLocation.CenterScreen; + window.Position = new Point(60, 40); + + window.Show(); + + var expectedPosition = new Point( + window.PlatformImpl.MaxClientSize.Width / 2 - window.ClientSize.Width / 2, + window.PlatformImpl.MaxClientSize.Height / 2 - window.ClientSize.Height / 2); + + Assert.Equal(window.Position, expectedPosition); + } + } + + [Fact] + public void Window_Should_Be_Centered_Relative_To_Owner_When_Window_Startup_Location_Is_Center_Owner() + { + var parentWindowImpl = new Mock(); + parentWindowImpl.SetupProperty(x => x.Position); + parentWindowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); + parentWindowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1920, 1080)); + parentWindowImpl.Setup(x => x.Scaling).Returns(1); + + var windowImpl = new Mock(); + windowImpl.SetupProperty(x => x.Position); + windowImpl.Setup(x => x.ClientSize).Returns(new Size(320, 200)); + windowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1920, 1080)); + windowImpl.Setup(x => x.Scaling).Returns(1); + + var parentWindowServices = TestServices.StyledWindow.With( + windowingPlatform: new MockWindowingPlatform(() => parentWindowImpl.Object)); + + var windowServices = TestServices.StyledWindow.With( + windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); + + using (UnitTestApplication.Start(parentWindowServices)) + { + var parentWindow = new Window(); + parentWindow.Position = new Point(60, 40); + + parentWindow.Show(); + + using (UnitTestApplication.Start(windowServices)) + { + var window = new Window(); + window.WindowStartupLocation = WindowStartupLocation.CenterOwner; + window.Position = new Point(60, 40); + window.Owner = parentWindow; + + window.Show(); + + var expectedPosition = new Point( + parentWindow.Position.X + parentWindow.ClientSize.Width / 2 - window.ClientSize.Width / 2, + parentWindow.Position.Y + parentWindow.ClientSize.Height / 2 - window.ClientSize.Height / 2); + + Assert.Equal(window.Position, expectedPosition); + } + } + } } } From 87e8cb892384127b7be314a7da8678bc1deefe44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sat, 8 Jul 2017 18:14:06 +0100 Subject: [PATCH 007/482] Fixed PossibleNullReferenceException. --- src/Avalonia.Controls/Window.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 3535510ce3..7f0cab3237 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -336,8 +336,11 @@ namespace Avalonia.Controls { if (WindowStartupLocation == WindowStartupLocation.CenterScreen) { - var positionAsSize = PlatformImpl.MaxClientSize / 2 - ClientSize / 2; - Position = new Point(positionAsSize.Width, positionAsSize.Height); + if (PlatformImpl != null) + { + var positionAsSize = PlatformImpl.MaxClientSize / 2 - ClientSize / 2; + Position = new Point(positionAsSize.Width, positionAsSize.Height); + } } else if (WindowStartupLocation == WindowStartupLocation.CenterOwner) { From e54d0c75d1c64f26be1aa6e67fede778729e0040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sun, 9 Jul 2017 14:03:22 +0100 Subject: [PATCH 008/482] Property changes. --- src/Avalonia.Controls/Window.cs | 13 +++++++++++-- src/Avalonia.Controls/WindowBase.cs | 9 +++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 7f0cab3237..f60768980d 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -78,9 +78,16 @@ namespace Avalonia.Controls public static readonly StyledProperty IconProperty = AvaloniaProperty.Register(nameof(Icon)); + /// + /// Defines the proeprty. + /// + public static readonly DirectProperty WindowStartupLocationProperty = + AvaloniaProperty.RegisterDirect(nameof(WindowStartupLocation), o => o.WindowStartupLocation); + private readonly NameScope _nameScope = new NameScope(); private object _dialogResult; private readonly Size _maxPlatformClientSize; + private WindowStartupLocation _windowStartupLoction; /// /// Initializes static members of the class. @@ -188,8 +195,8 @@ namespace Avalonia.Controls /// public WindowStartupLocation WindowStartupLocation { - get; - set; + get { return _windowStartupLoction; } + set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLoction, value); } } /// @@ -336,6 +343,8 @@ namespace Avalonia.Controls { if (WindowStartupLocation == WindowStartupLocation.CenterScreen) { + // This should be using a Screen API, but we don't have one yet and + // PlatformImpl.MaxClientSize is the best we have. if (PlatformImpl != null) { var positionAsSize = PlatformImpl.MaxClientSize / 2 - ClientSize / 2; diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index dd1e8fbef1..dd1e0ae842 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -32,12 +32,13 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty OwnerProperty = - AvaloniaProperty.Register(nameof(Owner)); + public static readonly DirectProperty OwnerProperty = + AvaloniaProperty.RegisterDirect(nameof(Owner), o => o.Owner); private bool _hasExecutedInitialLayoutPass; private bool _isActive; private bool _ignoreVisibilityChange; + private WindowBase _owner; static WindowBase() { @@ -111,8 +112,8 @@ namespace Avalonia.Controls /// public WindowBase Owner { - get { return GetValue(OwnerProperty); } - set { SetValue(OwnerProperty, value); } + get { return _owner; } + set { SetAndRaise(OwnerProperty, ref _owner, value); } } /// From efb9fd4c5732db991a6e70e7c410226bea68202c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sun, 6 Aug 2017 18:02:16 +0100 Subject: [PATCH 009/482] Added missing setters. --- src/Avalonia.Controls/Window.cs | 5 ++++- src/Avalonia.Controls/WindowBase.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index f60768980d..a94609122a 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -82,7 +82,10 @@ namespace Avalonia.Controls /// Defines the proeprty. /// public static readonly DirectProperty WindowStartupLocationProperty = - AvaloniaProperty.RegisterDirect(nameof(WindowStartupLocation), o => o.WindowStartupLocation); + AvaloniaProperty.RegisterDirect( + nameof(WindowStartupLocation), + o => o.WindowStartupLocation, + (o, v) => o.WindowStartupLocation = v); private readonly NameScope _nameScope = new NameScope(); private object _dialogResult; diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index dd1e0ae842..73d6d8c7cc 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -33,7 +33,10 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly DirectProperty OwnerProperty = - AvaloniaProperty.RegisterDirect(nameof(Owner), o => o.Owner); + AvaloniaProperty.RegisterDirect( + nameof(Owner), + o => o.Owner, + (o, v) => o.Owner = v); private bool _hasExecutedInitialLayoutPass; private bool _isActive; From a2551d505bcc5d4609e33ba8c3556fddced14fae Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 18 Aug 2017 18:50:29 +0300 Subject: [PATCH 010/482] Initial effort on Remote --- Avalonia.sln | 90 +++++++++++++++- samples/RemoteTest/Program.cs | 12 +++ samples/RemoteTest/RemoteTest.csproj | 25 +++++ .../Avalonia.Controls.csproj | 1 + .../Embedding/Offscreen/OffscreenTopLevel.cs | 63 +++++++++++ .../Offscreen/OffscreenTopLevelImpl.cs | 65 +++++++++++ src/Avalonia.Controls/Remote/RemoteServer.cs | 21 ++++ .../Remote/Server/RemoteServerTopLevelImpl.cs | 101 ++++++++++++++++++ src/Avalonia.Input/Key.cs | 5 +- .../Avalonia.Remote.Protocol.csproj | 9 ++ .../AvaloniaRemoteMessageGuidAttribute.cs | 19 ++++ .../DefaultMessageTypeResolver.cs | 35 ++++++ .../IMessageTypeResolver.cs | 10 ++ src/Avalonia.Remote.Protocol/ITransport.cs | 15 +++ src/Avalonia.Remote.Protocol/InputMessages.cs | 82 ++++++++++++++ .../ViewportMessages.cs | 54 ++++++++++ .../Platform/LockedFramebuffer.cs | 33 ++++++ 17 files changed, 638 insertions(+), 2 deletions(-) create mode 100644 samples/RemoteTest/Program.cs create mode 100644 samples/RemoteTest/RemoteTest.csproj create mode 100644 src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs create mode 100644 src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs create mode 100644 src/Avalonia.Controls/Remote/RemoteServer.cs create mode 100644 src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs create mode 100644 src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj create mode 100644 src/Avalonia.Remote.Protocol/AvaloniaRemoteMessageGuidAttribute.cs create mode 100644 src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs create mode 100644 src/Avalonia.Remote.Protocol/IMessageTypeResolver.cs create mode 100644 src/Avalonia.Remote.Protocol/ITransport.cs create mode 100644 src/Avalonia.Remote.Protocol/InputMessages.cs create mode 100644 src/Avalonia.Remote.Protocol/ViewportMessages.cs create mode 100644 src/Avalonia.Visuals/Platform/LockedFramebuffer.cs diff --git a/Avalonia.sln b/Avalonia.sln index 391d02382e..56c489fd0b 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}" EndProject @@ -185,6 +185,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests", "tests\Avalonia.RenderTests\Avalonia.Skia.RenderTests.csproj", "{E1582370-37B3-403C-917F-8209551B1634}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Remote.Protocol", "src\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj", "{D78A720C-C0C6-478B-8564-F167F9BDD01B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteTest", "samples\RemoteTest\RemoteTest.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -2514,6 +2518,86 @@ Global {E1582370-37B3-403C-917F-8209551B1634}.Release|Mono.Build.0 = Release|Any CPU {E1582370-37B3-403C-917F-8209551B1634}.Release|x86.ActiveCfg = Release|Any CPU {E1582370-37B3-403C-917F-8209551B1634}.Release|x86.Build.0 = Release|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|Mono.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|Mono.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|iPhone.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|Mono.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|Mono.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|x86.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.AppStore|x86.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|iPhone.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|Mono.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|Mono.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|x86.ActiveCfg = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Debug|x86.Build.0 = Debug|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|Any CPU.Build.0 = Release|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|iPhone.ActiveCfg = Release|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|iPhone.Build.0 = Release|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|Mono.ActiveCfg = Release|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|Mono.Build.0 = Release|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|x86.ActiveCfg = Release|Any CPU + {D78A720C-C0C6-478B-8564-F167F9BDD01B}.Release|x86.Build.0 = Release|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|Mono.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|Mono.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|iPhone.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|Mono.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|Mono.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|x86.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.AppStore|x86.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|iPhone.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|Mono.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|Mono.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|x86.ActiveCfg = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Debug|x86.Build.0 = Debug|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Any CPU.Build.0 = Release|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|iPhone.ActiveCfg = Release|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|iPhone.Build.0 = Release|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Mono.ActiveCfg = Release|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Mono.Build.0 = Release|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|x86.ActiveCfg = Release|Any CPU + {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2571,5 +2655,9 @@ Global {638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888} {E1582370-37B3-403C-917F-8209551B1634} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} + {E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1E8CA5AA-707A-4C57-A682-D265A49E10C3} EndGlobalSection EndGlobal diff --git a/samples/RemoteTest/Program.cs b/samples/RemoteTest/Program.cs new file mode 100644 index 0000000000..eb5f388e9e --- /dev/null +++ b/samples/RemoteTest/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace RemoteTest +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/samples/RemoteTest/RemoteTest.csproj b/samples/RemoteTest/RemoteTest.csproj new file mode 100644 index 0000000000..2487c66e41 --- /dev/null +++ b/samples/RemoteTest/RemoteTest.csproj @@ -0,0 +1,25 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 037546b186..47e0f88341 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -36,6 +36,7 @@ + diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs new file mode 100644 index 0000000000..cd2da692ba --- /dev/null +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Layout; +using Avalonia.Styling; + +namespace Avalonia.Controls.Embedding.Offscreen +{ + class OffscreenTopLevel : TopLevel, IStyleable + { + public OffscreenTopLevelImplBase Impl { get; } + + public OffscreenTopLevel(OffscreenTopLevelImplBase impl) : base(impl) + { + Impl = impl; + Prepare(); + } + + public void Prepare() + { + EnsureInitialized(); + ApplyTemplate(); + LayoutManager.Instance.ExecuteInitialLayoutPass(this); + } + + private void EnsureInitialized() + { + if (!this.IsInitialized) + { + var init = (ISupportInitialize)this; + init.BeginInit(); + init.EndInit(); + } + } + + private readonly NameScope _nameScope = new NameScope(); + public event EventHandler Registered + { + add { _nameScope.Registered += value; } + remove { _nameScope.Registered -= value; } + } + + public event EventHandler Unregistered + { + add { _nameScope.Unregistered += value; } + remove { _nameScope.Unregistered -= value; } + } + + public void Register(string name, object element) => _nameScope.Register(name, element); + + public object Find(string name) => _nameScope.Find(name); + + public void Unregister(string name) => _nameScope.Unregister(name); + + Type IStyleable.StyleKey => typeof(EmbeddableControlRoot); + public void Dispose() + { + PlatformImpl.Dispose(); + } + } +} diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs new file mode 100644 index 0000000000..088f20b270 --- /dev/null +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Platform; + +namespace Avalonia.Controls.Embedding.Offscreen +{ + abstract class OffscreenTopLevelImplBase : ITopLevelImpl + { + private double _scaling; + private Size _clientSize; + public IInputRoot InputRoot { get; private set; } + + public virtual void Dispose() + { + //No-op + } + + public abstract void Invalidate(Rect rect); + public abstract IEnumerable Surfaces { get; } + + public Size ClientSize + { + get { return _clientSize; } + set + { + _clientSize = value; + Resized?.Invoke(value); + } + } + + public double Scaling + { + get { return _scaling; } + set + { + _scaling = value; + ScalingChanged?.Invoke(value); + } + } + + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot; + + public virtual Point PointToClient(Point point) => point; + + public Point PointToScreen(Point point) + { + throw new NotImplementedException(); + } + + public virtual void SetCursor(IPlatformHandle cursor) + { + } + + public Action Closed { get; set; } + } +} diff --git a/src/Avalonia.Controls/Remote/RemoteServer.cs b/src/Avalonia.Controls/Remote/RemoteServer.cs new file mode 100644 index 0000000000..8c0509c5ba --- /dev/null +++ b/src/Avalonia.Controls/Remote/RemoteServer.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Remote.Protocol; + +namespace Avalonia.Controls.Remote +{ + public class RemoteServer + { + private readonly IAvaloniaRemoteTransport _transport; + + public RemoteServer(IAvaloniaRemoteTransport transport) + { + _transport = transport; + } + + public object Content { get; set; } + } +} diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs new file mode 100644 index 0000000000..6c89264c64 --- /dev/null +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Embedding.Offscreen; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; +using PixelFormat = Avalonia.Platform.PixelFormat; +using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; + +namespace Avalonia.Controls.Remote.Server +{ + class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface + { + private readonly IAvaloniaRemoteTransport _transport; + private LockedFramebuffer _framebuffer; + private object _lock = new object(); + private long _lastSentFrame; + private long _lastReceivedFrame = -1; + private bool _invalidated; + private ProtocolPixelFormat[] _supportedFormats; + + public RemoteServerTopLevelImpl(IAvaloniaRemoteTransport transport) + { + _transport = transport; + _transport.OnMessage += OnMessage; + } + + private void OnMessage(object obj) + { + lock (_lock) + { + var lastFrame = obj as FrameReceivedMessage; + if (lastFrame != null) + { + lock (_lock) + { + _lastReceivedFrame = lastFrame.SequenceId; + } + Dispatcher.UIThread.InvokeAsync(CheckNeedsRender); + } + var supportedFormats = obj as ClientSupportedPixelFormatsMessage; + if (supportedFormats != null) + _supportedFormats = supportedFormats.Formats; + } + } + + public override IEnumerable Surfaces => new[] { this }; + + FrameMessage RenderFrame(int width, int height, Size dpi, ProtocolPixelFormat? format) + { + var fmt = format ?? ProtocolPixelFormat.Rgba8888; + var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4; + var data = new byte[width * height * bpp]; + var handle = GCHandle.Alloc(data, GCHandleType.Pinned); + try + { + _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), width, height, width * bpp, dpi, (PixelFormat)fmt, + null); + Paint?.Invoke(new Rect(0, 0, width, height)); + } + finally + { + _framebuffer = null; + handle.Free(); + } + return new FrameMessage(); + } + + public ILockedFramebuffer Lock() + { + if (_framebuffer == null) + throw new InvalidOperationException("Paint was not requested, wait for Paint event"); + return _framebuffer; + } + + void CheckNeedsRender() + { + ProtocolPixelFormat[] formats; + lock (_lock) + { + if (_lastReceivedFrame != _lastSentFrame && !_invalidated) + return; + formats = _supportedFormats; + } + + //var frame = RenderFrame() + } + + public override void Invalidate(Rect rect) + { + _invalidated = true; + Dispatcher.UIThread.InvokeAsync(CheckNeedsRender); + } + } +} diff --git a/src/Avalonia.Input/Key.cs b/src/Avalonia.Input/Key.cs index 3ea506d79e..c10cbc0a6c 100644 --- a/src/Avalonia.Input/Key.cs +++ b/src/Avalonia.Input/Key.cs @@ -1,7 +1,10 @@ // 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. - +#if AVALONIA_REMOTE_PROTOCOL +namespace Avalonia.Remote.Protocol.Input +#else namespace Avalonia.Input +#endif { /// /// Defines the keys available on a keyboard. diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj new file mode 100644 index 0000000000..1ac7ab3a8b --- /dev/null +++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj @@ -0,0 +1,9 @@ + + + netstandard1.3 + AVALONIA_REMOTE_PROTOCOL;$(DefineConstants) + + + + + diff --git a/src/Avalonia.Remote.Protocol/AvaloniaRemoteMessageGuidAttribute.cs b/src/Avalonia.Remote.Protocol/AvaloniaRemoteMessageGuidAttribute.cs new file mode 100644 index 0000000000..12ee3178d3 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/AvaloniaRemoteMessageGuidAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Remote.Protocol +{ + [AttributeUsage(AttributeTargets.Class)] + public class AvaloniaRemoteMessageGuidAttribute : Attribute + { + public Guid Guid { get; } + + public AvaloniaRemoteMessageGuidAttribute(string guid) + { + Guid = Guid.Parse(guid); + } + } +} diff --git a/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs b/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs new file mode 100644 index 0000000000..9d44f33627 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Remote.Protocol +{ + public class DefaultMessageTypeResolver : IMessageTypeResolver + { + private readonly Dictionary _guidsToTypes = new Dictionary(); + private readonly Dictionary _typesToGuids = new Dictionary(); + public DefaultMessageTypeResolver(params Assembly[] assemblies) + { + foreach (var asm in + (assemblies ?? new Assembly[0]).Concat(new[] + {typeof(AvaloniaRemoteMessageGuidAttribute).GetTypeInfo().Assembly})) + { + foreach (var t in asm.ExportedTypes) + { + var attr = t.GetTypeInfo().GetCustomAttribute(); + if (attr != null) + { + _guidsToTypes[attr.Guid] = t; + _typesToGuids[t] = attr.Guid; + } + } + } + } + + public Type GetByGuid(Guid id) => _guidsToTypes[id]; + public Guid GetGuid(Type type) => _typesToGuids[type]; + } +} diff --git a/src/Avalonia.Remote.Protocol/IMessageTypeResolver.cs b/src/Avalonia.Remote.Protocol/IMessageTypeResolver.cs new file mode 100644 index 0000000000..2e2234b0d6 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/IMessageTypeResolver.cs @@ -0,0 +1,10 @@ +using System; + +namespace Avalonia.Remote.Protocol +{ + public interface IMessageTypeResolver + { + Type GetByGuid(Guid id); + Guid GetGuid(Type type); + } +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/ITransport.cs b/src/Avalonia.Remote.Protocol/ITransport.cs new file mode 100644 index 0000000000..e90a49ac71 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/ITransport.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Remote.Protocol +{ + public interface IAvaloniaRemoteTransport + { + Task Send(object data); + event Action OnMessage; + event Action OnException; + } +} diff --git a/src/Avalonia.Remote.Protocol/InputMessages.cs b/src/Avalonia.Remote.Protocol/InputMessages.cs new file mode 100644 index 0000000000..f1996372fc --- /dev/null +++ b/src/Avalonia.Remote.Protocol/InputMessages.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +/* + We are keeping copies of core events here, so they can be used + without referencing Avalonia itself, e. g. from projects that + are using WPF, GTK#, etc + */ +namespace Avalonia.Remote.Protocol.Input +{ + /// + /// Keep this in sync with InputModifiers in the main library + /// + [Flags] + public enum InputModifiers + { + Alt, + Control, + Shift, + Windows, + LeftMouseButton, + RightMouseButton, + MiddleMouseButton + } + + /// + /// Keep this in sync with InputModifiers in the main library + /// + public enum MouseButton + { + None, + Left, + Right, + Middle + } + + public abstract class InputEventMessageBase + { + public InputModifiers[] Modifiers { get; set; } + } + + public abstract class PointerEventMessageBase : InputEventMessageBase + { + public double X { get; set; } + public double Y { get; set; } + } + + [AvaloniaRemoteMessageGuid("6228F0B9-99F2-4F62-A621-414DA2881648")] + public class PointerMovedEventMessage : PointerEventMessageBase + { + + } + + [AvaloniaRemoteMessageGuid("7E9E2818-F93F-411A-800E-6B1AEB11DA46")] + public class PointerPressedEventMessage : PointerEventMessageBase + { + public MouseButton Button { get; set; } + } + + [AvaloniaRemoteMessageGuid("4ADC84EE-E7C8-4BCF-986C-DE3A2F78EDE4")] + public class PointerReleasedEventMessage : PointerEventMessageBase + { + public MouseButton Button { get; set; } + } + + [AvaloniaRemoteMessageGuid("79301A05-F02D-4B90-BB39-472563B504AE")] + public class ScrollEventMessage : PointerEventMessageBase + { + public double DeltaX { get; set; } + public double DeltaY { get; set; } + } + + [AvaloniaRemoteMessageGuid("1C3B691E-3D54-4237-BFB0-9FEA83BC1DB8")] + public class KeyEventMessage : InputEventMessageBase + { + public bool IsDown { get; set; } + public Key Key { get; set; } + } + +} diff --git a/src/Avalonia.Remote.Protocol/ViewportMessages.cs b/src/Avalonia.Remote.Protocol/ViewportMessages.cs new file mode 100644 index 0000000000..8e1b0bd99e --- /dev/null +++ b/src/Avalonia.Remote.Protocol/ViewportMessages.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Remote.Protocol.Viewport +{ + public enum PixelFormat + { + Rgb565, + Rgba8888, + Bgra8888 + } + + [AvaloniaRemoteMessageGuid("6E3C5310-E2B1-4C3D-8688-01183AA48C5B")] + public class MeasureViewportMessage + { + public double Width { get; set; } + public double Height { get; set; } + } + + [AvaloniaRemoteMessageGuid("BD7A8DE6-3DB8-4A13-8583-D6D4AB189A31")] + public class ClientViewportAllocatedMessage + { + public double Width { get; set; } + public double Height { get; set; } + } + + [AvaloniaRemoteMessageGuid("63481025-7016-43FE-BADC-F2FD0F88609E")] + public class ClientSupportedPixelFormatsMessage + { + public PixelFormat[] Formats { get; set; } + } + + [AvaloniaRemoteMessageGuid("68014F8A-289D-4851-8D34-5367EDA7F827")] + public class FrameReceivedMessage + { + public long SequenceId { get; set; } + } + + + [AvaloniaRemoteMessageGuid("F58313EE-FE69-4536-819D-F52EDF201A0E")] + public class FrameMessage + { + public long SequenceId { get; set; } + public PixelFormat Format { get; set; } + public byte[] Data { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public int Stride { get; set; } + } + +} diff --git a/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs b/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs new file mode 100644 index 0000000000..c03b714956 --- /dev/null +++ b/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs @@ -0,0 +1,33 @@ +using System; + +namespace Avalonia.Platform +{ + public class LockedFramebuffer : ILockedFramebuffer + { + private readonly Action _onDispose; + + public LockedFramebuffer(IntPtr address, int width, int height, int rowBytes, Vector dpi, PixelFormat format, + Action onDispose) + { + _onDispose = onDispose; + Address = address; + Width = width; + Height = height; + RowBytes = rowBytes; + Dpi = dpi; + Format = format; + } + + public IntPtr Address { get; } + public int Width { get; } + public int Height { get; } + public int RowBytes { get; } + public Vector Dpi { get; } + public PixelFormat Format { get; } + + public void Dispose() + { + _onDispose?.Invoke(); + } + } +} \ No newline at end of file From fe6e8a4d2b9f076d463b87d38e5f1451f83cb194 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 19 Aug 2017 10:53:29 +0300 Subject: [PATCH 011/482] [GTK3] Use lower priority for timers created by Signal() --- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 2 +- src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs | 6 ++++++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 4ea1a5af56..474f29733e 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -77,7 +77,7 @@ namespace Avalonia.Gtk3 if (!_signaled) { _signaled = true; - GlibTimeout.Add(0, () => + GlibTimeout.Add(100, 0, () => { lock (_lock) { diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs index 9971d8881d..1936d40086 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs @@ -35,6 +35,12 @@ namespace Avalonia.Gtk3.Interop var handle = GCHandle.Alloc(callback); Native.GTimeoutAdd(interval, PinnedHandler, GCHandle.ToIntPtr(handle)); } + + public static void Add(int priority, uint interval, Func callback) + { + var handle = GCHandle.Alloc(callback); + Native.GTimeoutAddFull(priority, interval, PinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero); + } class Timer : IDisposable { diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index fb1a9955e3..5ccdf3424c 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -241,6 +241,8 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public delegate ulong g_timeout_add(uint interval, timeout_callback callback, IntPtr data); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] + public delegate ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data, IntPtr destroy); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public delegate ulong g_free(IntPtr data); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public unsafe delegate void g_slist_free(GSList* data); @@ -302,6 +304,7 @@ namespace Avalonia.Gtk3.Interop 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.g_timeout_add_full GTimeoutAddFull; public static D.g_free GFree; public static D.g_slist_free GSlistFree; public static D.g_memory_input_stream_new_from_data GMemoryInputStreamNewFromData; From d384a7f61a20a55f31c0766af173fc0a15c6fa12 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 19 Aug 2017 10:54:27 +0300 Subject: [PATCH 012/482] Added .idea/* to .gitignore --- .gitignore | 1 + src/Avalonia.Remote.Protocol/BsonStreamTransport.cs | 7 +++++++ src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs | 7 +++++++ 3 files changed, 15 insertions(+) create mode 100644 src/Avalonia.Remote.Protocol/BsonStreamTransport.cs create mode 100644 src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs diff --git a/.gitignore b/.gitignore index 640725fa26..dc3de9e54c 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,4 @@ artifacts/ nuget Avalonia.XBuild.sln project.lock.json +.idea/* diff --git a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs new file mode 100644 index 0000000000..b16966ffd7 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs @@ -0,0 +1,7 @@ +namespace Avalonia.Remote.Protocol +{ + public class BsonStreamTransport + { + + } +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs new file mode 100644 index 0000000000..a1f1df2a2d --- /dev/null +++ b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs @@ -0,0 +1,7 @@ +namespace Avalonia.Remote.Protocol +{ + public class TransportConnectionWrapper + { + + } +} \ No newline at end of file From 646db5b91472e40b405728396c2d68e4d118a9e2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 19 Aug 2017 10:55:51 +0300 Subject: [PATCH 013/482] Initial working version of remoting --- samples/ControlCatalog/MainWindow.xaml.cs | 3 +- samples/RemoteTest/Program.cs | 43 ++++- samples/RemoteTest/RemoteTest.csproj | 2 +- src/Avalonia.Base/AvaloniaObject.cs | 1 + .../Offscreen/OffscreenTopLevelImpl.cs | 13 +- src/Avalonia.Controls/Remote/RemoteServer.cs | 26 ++- src/Avalonia.Controls/Remote/RemoteWidget.cs | 79 +++++++++ .../Remote/Server/RemoteServerTopLevelImpl.cs | 107 +++++++++--- .../Avalonia.Remote.Protocol.csproj | 19 ++- .../BsonStreamTransport.cs | 157 +++++++++++++++++- .../BsonTcpTransport.cs | 27 +++ src/Avalonia.Remote.Protocol/EventStash.cs | 72 ++++++++ src/Avalonia.Remote.Protocol/ITransport.cs | 2 +- .../TcpTransportBase.cs | 84 ++++++++++ .../TransportConnectionWrapper.cs | 98 ++++++++++- .../ViewportMessages.cs | 5 +- 16 files changed, 691 insertions(+), 47 deletions(-) create mode 100644 src/Avalonia.Controls/Remote/RemoteWidget.cs create mode 100644 src/Avalonia.Remote.Protocol/BsonTcpTransport.cs create mode 100644 src/Avalonia.Remote.Protocol/EventStash.cs create mode 100644 src/Avalonia.Remote.Protocol/TcpTransportBase.cs diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 413794dfa2..c7ff0a60df 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -6,11 +6,12 @@ namespace ControlCatalog { public class MainWindow : Window { + public static bool DebugMode = false; public MainWindow() { this.InitializeComponent(); this.AttachDevTools(); - Renderer.DrawDirtyRects = Renderer.DrawFps = true; + Renderer.DrawDirtyRects = Renderer.DrawFps = DebugMode; } private void InitializeComponent() diff --git a/samples/RemoteTest/Program.cs b/samples/RemoteTest/Program.cs index eb5f388e9e..dce168c7ea 100644 --- a/samples/RemoteTest/Program.cs +++ b/samples/RemoteTest/Program.cs @@ -1,4 +1,13 @@ using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Remote; +using Avalonia.Remote.Protocol; +using Avalonia.Threading; +using ControlCatalog; namespace RemoteTest { @@ -6,7 +15,39 @@ namespace RemoteTest { static void Main(string[] args) { - Console.WriteLine("Hello World!"); + AppBuilder.Configure().UsePlatformDetect().SetupWithoutStarting(); + + var l = new TcpListener(IPAddress.Loopback, 0); + l.Start(); + var port = ((IPEndPoint) l.LocalEndpoint).Port; + l.Stop(); + + var transport = new BsonTcpTransport(); + transport.Listen(IPAddress.Loopback, port, sc => + { + Dispatcher.UIThread.InvokeAsync(() => + { + new RemoteServer(sc).Content = new MainView(); + }); + }); + + var cts = new CancellationTokenSource(); + transport.Connect(IPAddress.Loopback, port).ContinueWith(t => + { + Dispatcher.UIThread.InvokeAsync(() => + { + var window = new Window() + { + Content = new RemoteWidget(t.Result) + }; + window.Closed += delegate { cts.Cancel(); }; + window.Show(); + }); + }); + Dispatcher.UIThread.MainLoop(cts.Token); + + + } } } diff --git a/samples/RemoteTest/RemoteTest.csproj b/samples/RemoteTest/RemoteTest.csproj index 2487c66e41..8682916503 100644 --- a/samples/RemoteTest/RemoteTest.csproj +++ b/samples/RemoteTest/RemoteTest.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + netcoreapp1.1 diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1bdf1eb5e3..f94b6519d5 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -56,6 +56,7 @@ namespace Avalonia /// public AvaloniaObject() { + CheckAccess(); foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this)) { object value = property.IsDirect ? diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 088f20b270..c986c5d07c 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -6,12 +6,13 @@ using System.Threading.Tasks; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; +using Avalonia.Rendering; namespace Avalonia.Controls.Embedding.Offscreen { - abstract class OffscreenTopLevelImplBase : ITopLevelImpl + public abstract class OffscreenTopLevelImplBase : ITopLevelImpl { - private double _scaling; + private double _scaling = 1; private Size _clientSize; public IInputRoot InputRoot { get; private set; } @@ -20,6 +21,8 @@ namespace Avalonia.Controls.Embedding.Offscreen //No-op } + public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root); + public abstract void Invalidate(Rect rect); public abstract IEnumerable Surfaces { get; } @@ -51,15 +54,13 @@ namespace Avalonia.Controls.Embedding.Offscreen public virtual Point PointToClient(Point point) => point; - public Point PointToScreen(Point point) - { - throw new NotImplementedException(); - } + public virtual Point PointToScreen(Point point) => point; public virtual void SetCursor(IPlatformHandle cursor) { } public Action Closed { get; set; } + public abstract IMouseDevice MouseDevice { get; } } } diff --git a/src/Avalonia.Controls/Remote/RemoteServer.cs b/src/Avalonia.Controls/Remote/RemoteServer.cs index 8c0509c5ba..0bc43069e7 100644 --- a/src/Avalonia.Controls/Remote/RemoteServer.cs +++ b/src/Avalonia.Controls/Remote/RemoteServer.cs @@ -3,19 +3,37 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Avalonia.Controls.Embedding; +using Avalonia.Controls.Remote.Server; +using Avalonia.Platform; using Avalonia.Remote.Protocol; namespace Avalonia.Controls.Remote { public class RemoteServer { - private readonly IAvaloniaRemoteTransport _transport; + private EmbeddableControlRoot _topLevel; - public RemoteServer(IAvaloniaRemoteTransport transport) + class TopLevelImpl : RemoteServerTopLevelImpl, IEmbeddableWindowImpl { - _transport = transport; + public TopLevelImpl(IAvaloniaRemoteTransportConnection transport) : base(transport) + { + } + + public event Action LostFocus; + } + + public RemoteServer(IAvaloniaRemoteTransportConnection transport) + { + _topLevel = new EmbeddableControlRoot(new TopLevelImpl(transport)); + _topLevel.Prepare(); + //TODO: Somehow react on closed connection? } - public object Content { get; set; } + public object Content + { + get => _topLevel.Content; + set => _topLevel.Content = value; + } } } diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs new file mode 100644 index 0000000000..f4bcb37d48 --- /dev/null +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -0,0 +1,79 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; +using PixelFormat = Avalonia.Platform.PixelFormat; + +namespace Avalonia.Controls.Remote +{ + public class RemoteWidget : Control + { + private readonly IAvaloniaRemoteTransportConnection _connection; + private FrameMessage _lastFrame; + private WritableBitmap _bitmap; + public RemoteWidget(IAvaloniaRemoteTransportConnection connection) + { + _connection = connection; + _connection.OnMessage += msg => Dispatcher.UIThread.InvokeAsync(() => OnMessage(msg)); + _connection.Send(new ClientSupportedPixelFormatsMessage + { + Formats = new[] + { + Avalonia.Remote.Protocol.Viewport.PixelFormat.Bgra8888, + Avalonia.Remote.Protocol.Viewport.PixelFormat.Rgba8888, + } + }); + } + + private void OnMessage(object msg) + { + if (msg is FrameMessage frame) + { + _connection.Send(new FrameReceivedMessage + { + SequenceId = frame.SequenceId + }); + _lastFrame = frame; + InvalidateVisual(); + } + + } + + protected override void ArrangeCore(Rect finalRect) + { + _connection.Send(new ClientViewportAllocatedMessage + { + Width = finalRect.Width, + Height = finalRect.Height, + DpiX = 96, + DpiY = 96 //TODO: Somehow detect the actual DPI + }); + base.ArrangeCore(finalRect); + } + + public override void Render(DrawingContext context) + { + if (_lastFrame != null) + { + var fmt = (PixelFormat) _lastFrame.Format; + if (_bitmap == null || _bitmap.PixelWidth != _lastFrame.Width || + _bitmap.PixelHeight != _lastFrame.Height) + _bitmap = new WritableBitmap(_lastFrame.Width, _lastFrame.Height, fmt); + using (var l = _bitmap.Lock()) + { + var lineLen = (fmt == PixelFormat.Rgb565 ? 2 : 4) * _lastFrame.Width; + for (var y = 0; y < _lastFrame.Height; y++) + Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride, + new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen); + } + context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelWidth, _bitmap.PixelHeight), + new Rect(Bounds.Size)); + } + base.Render(context); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index 6c89264c64..be97f7caaa 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -6,6 +6,8 @@ using System.Text; using System.Threading.Tasks; using Avalonia.Controls.Embedding.Offscreen; using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Input; +using Avalonia.Layout; using Avalonia.Platform; using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Viewport; @@ -15,17 +17,20 @@ using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; namespace Avalonia.Controls.Remote.Server { - class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface + public class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface { - private readonly IAvaloniaRemoteTransport _transport; + private readonly IAvaloniaRemoteTransportConnection _transport; private LockedFramebuffer _framebuffer; private object _lock = new object(); - private long _lastSentFrame; + private long _lastSentFrame = -1; private long _lastReceivedFrame = -1; + private long _nextFrameNumber = 1; + private ClientViewportAllocatedMessage _pendingAllocation; private bool _invalidated; + private Vector _dpi = new Vector(96, 96); private ProtocolPixelFormat[] _supportedFormats; - public RemoteServerTopLevelImpl(IAvaloniaRemoteTransport transport) + public RemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport) { _transport = transport; _transport.OnMessage += OnMessage; @@ -35,24 +40,64 @@ namespace Avalonia.Controls.Remote.Server { lock (_lock) { - var lastFrame = obj as FrameReceivedMessage; - if (lastFrame != null) + if (obj is FrameReceivedMessage lastFrame) { lock (_lock) { _lastReceivedFrame = lastFrame.SequenceId; } - Dispatcher.UIThread.InvokeAsync(CheckNeedsRender); + Dispatcher.UIThread.InvokeAsync(RenderIfNeeded); + } + if (obj is ClientSupportedPixelFormatsMessage supportedFormats) + { + lock (_lock) + _supportedFormats = supportedFormats.Formats; + Dispatcher.UIThread.InvokeAsync(RenderIfNeeded); + } + if (obj is MeasureViewportMessage measure) + Dispatcher.UIThread.InvokeAsync(() => + { + var m = Measure(new Size(measure.Width, measure.Height)); + _transport.Send(new MeasureViewportMessage + { + Width = m.Width, + Height = m.Height + }); + }); + if (obj is ClientViewportAllocatedMessage allocated) + { + lock (_lock) + { + if (_pendingAllocation == null) + Dispatcher.UIThread.InvokeAsync(() => + { + ClientViewportAllocatedMessage allocation; + lock (_lock) + { + allocation = _pendingAllocation; + _pendingAllocation = null; + } + _dpi = new Vector(allocation.DpiX, allocation.DpiY); + ClientSize = new Size(allocation.Width, allocation.Height); + RenderIfNeeded(); + }); + + _pendingAllocation = allocated; + } } - var supportedFormats = obj as ClientSupportedPixelFormatsMessage; - if (supportedFormats != null) - _supportedFormats = supportedFormats.Formats; } } + protected virtual Size Measure(Size constaint) + { + var l = (ILayoutable) InputRoot; + l.Measure(constaint); + return l.DesiredSize; + } + public override IEnumerable Surfaces => new[] { this }; - FrameMessage RenderFrame(int width, int height, Size dpi, ProtocolPixelFormat? format) + FrameMessage RenderFrame(int width, int height, ProtocolPixelFormat? format) { var fmt = format ?? ProtocolPixelFormat.Rgba8888; var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4; @@ -60,7 +105,7 @@ namespace Avalonia.Controls.Remote.Server var handle = GCHandle.Alloc(data, GCHandleType.Pinned); try { - _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), width, height, width * bpp, dpi, (PixelFormat)fmt, + _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), width, height, width * bpp, _dpi, (PixelFormat)fmt, null); Paint?.Invoke(new Rect(0, 0, width, height)); } @@ -69,7 +114,14 @@ namespace Avalonia.Controls.Remote.Server _framebuffer = null; handle.Free(); } - return new FrameMessage(); + return new FrameMessage + { + Data = data, + Format = (ProtocolPixelFormat) format, + Width = width, + Height = height, + Stride = width * bpp, + }; } public ILockedFramebuffer Lock() @@ -79,23 +131,40 @@ namespace Avalonia.Controls.Remote.Server return _framebuffer; } - void CheckNeedsRender() + void RenderIfNeeded() { - ProtocolPixelFormat[] formats; lock (_lock) { - if (_lastReceivedFrame != _lastSentFrame && !_invalidated) + if (_lastReceivedFrame != _lastSentFrame || !_invalidated || _supportedFormats == null) return; - formats = _supportedFormats; + } + if (ClientSize.Width < 1 || ClientSize.Height < 1) + return; + var format = ProtocolPixelFormat.Rgba8888; + foreach(var fmt in _supportedFormats) + if (fmt <= ProtocolPixelFormat.MaxValue) + { + format = fmt; + break; + } - //var frame = RenderFrame() + var frame = RenderFrame((int) ClientSize.Width, (int) ClientSize.Height, format); + lock (_lock) + { + _lastSentFrame = _nextFrameNumber++; + frame.SequenceId = _lastSentFrame; + _invalidated = false; + } + _transport.Send(frame); } public override void Invalidate(Rect rect) { _invalidated = true; - Dispatcher.UIThread.InvokeAsync(CheckNeedsRender); + Dispatcher.UIThread.InvokeAsync(RenderIfNeeded); } + + public override IMouseDevice MouseDevice { get; } = new MouseDevice(); } } diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj index 1ac7ab3a8b..a9c6b9e9a0 100644 --- a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj +++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj @@ -1,9 +1,10 @@ - - - netstandard1.3 - AVALONIA_REMOTE_PROTOCOL;$(DefineConstants) - - - - - + + + netstandard1.3 + AVALONIA_REMOTE_PROTOCOL;$(DefineConstants) + + + + + + \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs index b16966ffd7..8c983c385d 100644 --- a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs +++ b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs @@ -1,7 +1,160 @@ -namespace Avalonia.Remote.Protocol +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using Newtonsoft.Json; +using System.Threading.Tasks; +using Newtonsoft.Json.Bson; + +namespace Avalonia.Remote.Protocol { - public class BsonStreamTransport + public class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection { + private readonly IMessageTypeResolver _resolver; + private readonly Stream _inputStream; + private readonly Stream _outputStream; + private readonly Action _disposeCallback; + private readonly CancellationToken _cancel; + private readonly CancellationTokenSource _cancelSource; + private readonly MemoryStream _outputBlock = new MemoryStream(); + private readonly object _lock = new object(); + private bool _writeOperationPending; + private bool _readingAlreadyStarted; + private bool _writerIsBroken; + static readonly JsonSerializer Serializer = new JsonSerializer(); + private static readonly byte[] ZeroLength = new byte[4]; + + public BsonStreamTransportConnection(IMessageTypeResolver resolver, Stream inputStream, Stream outputStream, Action disposeCallback) + { + _resolver = resolver; + _inputStream = inputStream; + _outputStream = outputStream; + _disposeCallback = disposeCallback; + _cancelSource = new CancellationTokenSource(); + _cancel = _cancelSource.Token; + } + + public void Dispose() + { + _cancelSource.Cancel(); + _disposeCallback?.Invoke(); + } + public void StartReading() + { + lock (_lock) + { + if(_readingAlreadyStarted) + throw new InvalidOperationException("Reading has already started"); + _readingAlreadyStarted = true; + Task.Run(Reader, _cancel); + } + } + + async Task ReadExact(byte[] buffer) + { + int read = 0; + while (read != buffer.Length) + { + var readNow = await _inputStream.ReadAsync(buffer, read, buffer.Length - read, _cancel) + .ConfigureAwait(false); + if (readNow == 0) + throw new EndOfStreamException(); + read += readNow; + } + } + + async Task Reader() + { + Task.Yield(); + try + { + while (true) + { + var infoBlock = new byte[20]; + await ReadExact(infoBlock).ConfigureAwait(false); + var length = BitConverter.ToInt32(infoBlock, 0); + var guidBytes = new byte[16]; + Buffer.BlockCopy(infoBlock, 4, guidBytes, 0, 16); + var guid = new Guid(guidBytes); + var buffer = new byte[length]; + await ReadExact(buffer).ConfigureAwait(false); + if (Environment.GetEnvironmentVariable("WTF") == "WTF") + { + + using (var f = System.IO.File.Create("/tmp/wtf2.bin")) + { + f.Write(infoBlock, 0, infoBlock.Length); + f.Write(buffer, 0, buffer.Length); + } + } + var message = Serializer.Deserialize(new BsonReader(new MemoryStream(buffer)), _resolver.GetByGuid(guid)); + OnMessage?.Invoke(message); + } + } + catch (Exception e) + { + FireException(e); + } + } + + + public async Task Send(object data) + { + lock (_lock) + { + if(_writerIsBroken) //Ignore further calls, since there is no point of writing to "broken" stream + return; + if (_writeOperationPending) + throw new InvalidOperationException("Previous send operation was not finished"); + _writeOperationPending = true; + } + try + { + var guid = _resolver.GetGuid(data.GetType()).ToByteArray(); + _outputBlock.Seek(0, SeekOrigin.Begin); + _outputBlock.SetLength(0); + _outputBlock.Write(ZeroLength, 0, 4); + _outputBlock.Write(guid, 0, guid.Length); + var writer = new BsonWriter(_outputBlock); + Serializer.Serialize(writer, data); + _outputBlock.Seek(0, SeekOrigin.Begin); + var length = BitConverter.GetBytes((int)_outputBlock.Length - 20); + _outputBlock.Write(length, 0, length.Length); + _outputBlock.Seek(0, SeekOrigin.Begin); + + try + { + await _outputBlock.CopyToAsync(_outputStream, 0x1000, _cancel).ConfigureAwait(false); + } + catch (Exception e) //We are only catching "network"-related exceptions here + { + lock (_lock) + { + _writerIsBroken = true; + } + FireException(e); + } + } + finally + { + lock (_lock) + { + _writeOperationPending = false; + } + } + } + + void FireException(Exception e) + { + var cancel = e as OperationCanceledException; + if (cancel?.CancellationToken == _cancel) + return; + OnException?.Invoke(e); + } + + + public event Action OnMessage; + public event Action OnException; } } \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs b/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs new file mode 100644 index 0000000000..e647f75997 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/BsonTcpTransport.cs @@ -0,0 +1,27 @@ +using System; +using System.IO; +using System.Reflection; + +namespace Avalonia.Remote.Protocol +{ + public class BsonTcpTransport : TcpTransportBase + { + public BsonTcpTransport(IMessageTypeResolver resolver) : base(resolver) + { + } + + public BsonTcpTransport() : this(new DefaultMessageTypeResolver(typeof(BsonTcpTransport).GetTypeInfo().Assembly)) + { + + } + + protected override IAvaloniaRemoteTransportConnection CreateTransport(IMessageTypeResolver resolver, + Stream stream, Action dispose) + { + var t = new BsonStreamTransportConnection(resolver, stream, stream, dispose); + var wrap = new TransportConnectionWrapper(t); + t.StartReading(); + return wrap; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/EventStash.cs b/src/Avalonia.Remote.Protocol/EventStash.cs new file mode 100644 index 0000000000..06840c8253 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/EventStash.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; + +namespace Avalonia.Remote.Protocol +{ + public class EventStash + { + private readonly Action _exceptionHandler; + private List _stash; + private Action _delegate; + + public EventStash(Action exceptionHandler = null) + { + _exceptionHandler = exceptionHandler; + } + + public void Add(Action handler) + { + List stash; + lock (this) + { + var needsReplay = _delegate == null; + _delegate += handler; + if(!needsReplay) + return; + + lock (this) + { + stash = _stash; + if(_stash == null) + return; + _stash = null; + } + } + foreach (var m in stash) + { + if (_exceptionHandler != null) + try + { + _delegate?.Invoke(m); + } + catch (Exception e) + { + _exceptionHandler(e); + } + else + _delegate?.Invoke(m); + } + } + + + public void Remove(Action handler) + { + lock (this) + _delegate -= handler; + } + + public void Fire(T ev) + { + if (_delegate == null) + { + lock (this) + { + _stash = _stash ?? new List(); + _stash.Add(ev); + } + } + else + _delegate?.Invoke(ev); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/ITransport.cs b/src/Avalonia.Remote.Protocol/ITransport.cs index e90a49ac71..d4e5427cb4 100644 --- a/src/Avalonia.Remote.Protocol/ITransport.cs +++ b/src/Avalonia.Remote.Protocol/ITransport.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Avalonia.Remote.Protocol { - public interface IAvaloniaRemoteTransport + public interface IAvaloniaRemoteTransportConnection : IDisposable { Task Send(object data); event Action OnMessage; diff --git a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs new file mode 100644 index 0000000000..4278badaae --- /dev/null +++ b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs @@ -0,0 +1,84 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace Avalonia.Remote.Protocol +{ + public abstract class TcpTransportBase + { + private readonly IMessageTypeResolver _resolver; + + public TcpTransportBase(IMessageTypeResolver resolver) + { + _resolver = resolver; + } + + protected abstract IAvaloniaRemoteTransportConnection CreateTransport(IMessageTypeResolver resolver, + Stream stream, Action disposeCallback); + + class DisposableServer : IDisposable + { + private readonly TcpListener _l; + + public DisposableServer(TcpListener l) + { + _l = l; + } + public void Dispose() + { + try + { + _l.Stop(); + } + catch + { + //Ignore + } + } + } + + public IDisposable Listen(IPAddress address, int port, Action cb) + { + var server = new TcpListener(address, port); + async void AcceptNew() + { + try + { + var cl = await server.AcceptTcpClientAsync(); + AcceptNew(); + Task.Run(async () => + { + try + { + var tcs = new TaskCompletionSource(); + var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0)); + cb(t); + await tcs.Task; + } + finally + { + cl.Dispose(); + } + + }); + } + catch + { + //Ignore and stop + } + } + server.Start(); + AcceptNew(); + return new DisposableServer(server); + } + + public async Task Connect(IPAddress address, int port) + { + var c = new TcpClient(); + await c.ConnectAsync(address, port); + return CreateTransport(_resolver, c.GetStream(), c.Dispose); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs index a1f1df2a2d..b01b6d7303 100644 --- a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs +++ b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs @@ -1,7 +1,101 @@ -namespace Avalonia.Remote.Protocol +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Avalonia.Remote.Protocol { - public class TransportConnectionWrapper + public class TransportConnectionWrapper : IAvaloniaRemoteTransportConnection { + private readonly IAvaloniaRemoteTransportConnection _conn; + private EventStash _onMessage; + private EventStash _onException = new EventStash(); + private Queue _sendQueue = new Queue(); + private object _lock =new object(); + private TaskCompletionSource _signal; + private bool _workerIsAlive; + public TransportConnectionWrapper(IAvaloniaRemoteTransportConnection conn) + { + _conn = conn; + _onMessage = new EventStash(_onException.Fire); + _conn.OnException +=_onException.Fire; + conn.OnMessage += _onMessage.Fire; + + } + + class SendOperation + { + public object Message { get; set; } + public TaskCompletionSource Tcs { get; set; } + } + + public void Dispose() => _conn.Dispose(); + + async void Worker() + { + while (true) + { + SendOperation wi = null; + lock (_lock) + { + if (_sendQueue.Count != 0) + wi = _sendQueue.Dequeue(); + } + if (wi == null) + { + var signal = new TaskCompletionSource(); + lock (_lock) + _signal = signal; + await signal.Task.ConfigureAwait(false); + continue; + } + try + { + await _conn.Send(wi.Message).ConfigureAwait(false); + wi.Tcs.TrySetResult(0); + } + catch (Exception e) + { + wi.Tcs.TrySetException(e); + } + } + + } + + public Task Send(object data) + { + var tcs = new TaskCompletionSource(); + lock (_lock) + { + if (!_workerIsAlive) + { + _workerIsAlive = true; + Worker(); + } + _sendQueue.Enqueue(new SendOperation + { + Message = data, + Tcs = tcs + }); + if (_signal != null) + { + _signal.SetResult(0); + _signal = null; + } + } + return tcs.Task; + } + + public event Action OnMessage + { + add => _onMessage.Add(value); + remove => _onMessage.Remove(value); + } + + public event Action OnException + { + add => _onException.Add(value); + remove => _onException.Remove(value); + } } } \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/ViewportMessages.cs b/src/Avalonia.Remote.Protocol/ViewportMessages.cs index 8e1b0bd99e..82a7688f37 100644 --- a/src/Avalonia.Remote.Protocol/ViewportMessages.cs +++ b/src/Avalonia.Remote.Protocol/ViewportMessages.cs @@ -10,7 +10,8 @@ namespace Avalonia.Remote.Protocol.Viewport { Rgb565, Rgba8888, - Bgra8888 + Bgra8888, + MaxValue = Bgra8888 } [AvaloniaRemoteMessageGuid("6E3C5310-E2B1-4C3D-8688-01183AA48C5B")] @@ -25,6 +26,8 @@ namespace Avalonia.Remote.Protocol.Viewport { public double Width { get; set; } public double Height { get; set; } + public double DpiX { get; set; } + public double DpiY { get; set; } } [AvaloniaRemoteMessageGuid("63481025-7016-43FE-BADC-F2FD0F88609E")] From e7247a2c12f383d7cd6e26136a1d96fab9f3102f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 29 Aug 2017 01:17:19 +0200 Subject: [PATCH 014/482] Added methods for creating D2D render layers. Craetes a compatible render target depending on the type of `IRenderTargetBitmapImpl`. --- .../Direct3DInteropSample/MainWindow.cs | 3 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 4 +- .../ExternalRenderTarget.cs | 15 ++-- .../Avalonia.Direct2D1/ICreateLayer.cs | 10 +++ .../Media/DrawingContextImpl.cs | 13 ++-- .../Media/ImageBrushImpl.cs | 7 +- .../Media/Imaging/BitmapImpl.cs | 32 +++++++-- .../Media/Imaging/D2DBitmapImpl.cs | 47 +++++++------ .../Imaging/D2DRenderTargetBitmapImpl.cs | 68 +++++++++++++++++++ .../Media/Imaging/WicBitmapImpl.cs | 57 +++++----------- ...apImpl.cs => WicRenderTargetBitmapImpl.cs} | 6 +- .../Avalonia.Direct2D1/OptionalDispose.cs | 22 ++++++ .../Avalonia.Direct2D1/RenderTarget.cs | 22 ++---- .../SwapChainRenderTarget.cs | 40 ++++++----- 14 files changed, 225 insertions(+), 121 deletions(-) create mode 100644 src/Windows/Avalonia.Direct2D1/ICreateLayer.cs create mode 100644 src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs rename src/Windows/Avalonia.Direct2D1/Media/Imaging/{RenderTargetBitmapImpl.cs => WicRenderTargetBitmapImpl.cs} (89%) create mode 100644 src/Windows/Avalonia.Direct2D1/OptionalDispose.cs diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs index ad40e81895..3fe3b896f6 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -254,7 +254,8 @@ namespace Direct3DInteropSample public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { return new DrawingContextImpl(visualBrushRenderer, _window._d2dRenderTarget, - AvaloniaLocator.Current.GetService()); + AvaloniaLocator.Current.GetService(), + AvaloniaLocator.Current.GetService()); } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index fd8364c03b..eedb8a0c4e 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -145,7 +145,7 @@ namespace Avalonia.Direct2D1 return new HwndRenderTarget(nativeWindow); } if (s is IExternalDirect2DRenderTargetSurface external) - return new ExternalRenderTarget(external, s_dwfactory); + return new ExternalRenderTarget(external, s_dwfactory, s_imagingFactory); } throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); } @@ -156,7 +156,7 @@ namespace Avalonia.Direct2D1 double dpiX, double dpiY) { - return new RenderTargetBitmapImpl( + return new WicRenderTargetBitmapImpl( s_imagingFactory, s_d2D1Factory, s_dwfactory, diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs index 307048f7b4..d735c5f8bb 100644 --- a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Avalonia.Direct2D1.Media; using Avalonia.Platform; using Avalonia.Rendering; @@ -15,11 +11,16 @@ namespace Avalonia.Direct2D1 { private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider; private readonly DirectWriteFactory _dwFactory; - public ExternalRenderTarget(IExternalDirect2DRenderTargetSurface externalRenderTargetProvider, - DirectWriteFactory dwFactory) + private readonly SharpDX.WIC.ImagingFactory _wicFactory; + + public ExternalRenderTarget( + IExternalDirect2DRenderTargetSurface externalRenderTargetProvider, + DirectWriteFactory dwFactory, + SharpDX.WIC.ImagingFactory wicFactory) { _externalRenderTargetProvider = externalRenderTargetProvider; _dwFactory = dwFactory; + _wicFactory = wicFactory; } public void Dispose() @@ -31,7 +32,7 @@ namespace Avalonia.Direct2D1 { var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); _externalRenderTargetProvider.BeforeDrawing(); - return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, null, () => + return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, _wicFactory, null, () => { try { diff --git a/src/Windows/Avalonia.Direct2D1/ICreateLayer.cs b/src/Windows/Avalonia.Direct2D1/ICreateLayer.cs new file mode 100644 index 0000000000..9214377e6f --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/ICreateLayer.cs @@ -0,0 +1,10 @@ +using System; +using Avalonia.Platform; + +namespace Avalonia.Direct2D1 +{ + internal interface ICreateLayer + { + IRenderTargetBitmapImpl CreateLayer(int pixelWidth, int pixelHeight); + } +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 69b582b009..a7bb73e19f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -2,16 +2,13 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections; using System.Collections.Generic; using Avalonia.Media; using Avalonia.Platform; -using Avalonia.RenderHelpers; using Avalonia.Rendering; using SharpDX; using SharpDX.Direct2D1; using SharpDX.Mathematics.Interop; -using IBitmap = Avalonia.Media.Imaging.IBitmap; namespace Avalonia.Direct2D1.Media { @@ -24,6 +21,7 @@ namespace Avalonia.Direct2D1.Media private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly Action _finishedCallback; + private readonly SharpDX.WIC.ImagingFactory _imagingFactory; private SharpDX.DirectWrite.Factory _directWriteFactory; /// @@ -32,12 +30,14 @@ namespace Avalonia.Direct2D1.Media /// The visual brush renderer. /// The render target to draw to. /// The DirectWrite factory. + /// The WIC imaging factory. /// An optional swap chain associated with this drawing context. /// An optional delegate to be called when context is disposed. public DrawingContextImpl( IVisualBrushRenderer visualBrushRenderer, SharpDX.Direct2D1.RenderTarget renderTarget, SharpDX.DirectWrite.Factory directWriteFactory, + SharpDX.WIC.ImagingFactory imagingFactory, SharpDX.DXGI.SwapChain1 swapChain = null, Action finishedCallback = null) { @@ -46,6 +46,7 @@ namespace Avalonia.Direct2D1.Media _swapChain = swapChain; _finishedCallback = finishedCallback; _directWriteFactory = directWriteFactory; + _imagingFactory = imagingFactory; _swapChain = swapChain; _renderTarget.BeginDraw(); } @@ -97,7 +98,7 @@ namespace Avalonia.Direct2D1.Media using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget)) { _renderTarget.DrawBitmap( - d2d, + d2d.Value, destRect.ToSharpDX(), (float)opacity, BitmapInterpolationMode.Linear, @@ -115,7 +116,7 @@ namespace Avalonia.Direct2D1.Media public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { using (var d2dSource = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget)) - using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource)) + using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value)) using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D())) { @@ -397,7 +398,7 @@ namespace Avalonia.Direct2D1.Media return new ImageBrushImpl( visualBrush, _renderTarget, - new D2DBitmapImpl(intermediate.Bitmap), + new D2DBitmapImpl(_imagingFactory, intermediate.Bitmap), destinationSize); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs index ed3d78b4fd..08cfed2ace 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs @@ -10,6 +10,8 @@ namespace Avalonia.Direct2D1.Media { public sealed class ImageBrushImpl : BrushImpl { + OptionalDispose _bitmap; + public ImageBrushImpl( ITileBrush brush, SharpDX.Direct2D1.RenderTarget target, @@ -20,9 +22,10 @@ namespace Avalonia.Direct2D1.Media if (!calc.NeedsIntermediate) { + _bitmap = bitmap.GetDirect2DBitmap(target); PlatformBrush = new BitmapBrush( target, - bitmap.GetDirect2DBitmap(target), + _bitmap.Value, GetBitmapBrushProperties(brush), GetBrushProperties(brush, calc.DestinationRect)); } @@ -41,7 +44,7 @@ namespace Avalonia.Direct2D1.Media public override void Dispose() { - ((BitmapBrush)PlatformBrush)?.Bitmap.Dispose(); + _bitmap.Dispose(); base.Dispose(); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index 63596bdf54..d58f023391 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -1,20 +1,38 @@ 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; +using SharpDX.WIC; +using D2DBitmap = SharpDX.Direct2D1.Bitmap; namespace Avalonia.Direct2D1.Media { public abstract class BitmapImpl : IBitmapImpl, IDisposable { - public abstract Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target); + public BitmapImpl(ImagingFactory imagingFactory) + { + WicImagingFactory = imagingFactory; + } + + public ImagingFactory WicImagingFactory { get; } public abstract int PixelWidth { get; } public abstract int PixelHeight { get; } - public abstract void Save(string fileName); + + public abstract OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target); + + 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 abstract void Save(Stream stream); public virtual void Dispose() diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index 5378ae3257..b03e022674 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -1,11 +1,10 @@ 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; +using WICFactory = SharpDX.WIC.ImagingFactory; +using ImagingFactory2 = SharpDX.WIC.ImagingFactory2; +using ImageParameters = SharpDX.WIC.ImageParameters; +using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder; namespace Avalonia.Direct2D1.Media { @@ -26,32 +25,42 @@ namespace Avalonia.Direct2D1.Media /// or if the render target is a , /// the device associated with this context, to be renderable. /// - public D2DBitmapImpl(Bitmap d2DBitmap) + public D2DBitmapImpl(WICFactory imagingFactory, Bitmap d2DBitmap) + : base(imagingFactory) { - if (d2DBitmap == null) throw new ArgumentNullException(nameof(d2DBitmap)); - - _direct2D = d2DBitmap; + _direct2D = d2DBitmap ?? throw new ArgumentNullException(nameof(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) + public override void Dispose() { - throw new NotImplementedException(); + base.Dispose(); + _direct2D.Dispose(); } - public override void Save(Stream stream) + public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) { - throw new NotImplementedException(); + return new OptionalDispose(_direct2D, false); } - public override void Dispose() + public override void Save(Stream stream) { - base.Dispose(); - _direct2D.Dispose(); + using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream)) + using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder)) + using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)WicImagingFactory, null)) + { + var parameters = new ImageParameters( + new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied), + _direct2D.DotsPerInch.Width, + _direct2D.DotsPerInch.Height, + 0, 0, PixelWidth, PixelHeight); + + imageEncoder.WriteFrame(_direct2D, frameEncode, parameters); + frameEncode.Commit(); + encoder.Commit(); + } } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs new file mode 100644 index 0000000000..6162d57bad --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -0,0 +1,68 @@ +using System; +using Avalonia.Platform; +using Avalonia.Rendering; +using SharpDX; +using SharpDX.Direct2D1; +using SharpDX.WIC; +using D2DBitmap = SharpDX.Direct2D1.Bitmap; +using DirectWriteFactory = SharpDX.DirectWrite.Factory; + +namespace Avalonia.Direct2D1.Media.Imaging +{ + public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ICreateLayer + { + private readonly DirectWriteFactory _dwriteFactory; + private readonly BitmapRenderTarget _target; + + public D2DRenderTargetBitmapImpl( + ImagingFactory imagingFactory, + DirectWriteFactory dwriteFactory, + BitmapRenderTarget target) + : base(imagingFactory, target.Bitmap) + { + _dwriteFactory = dwriteFactory; + _target = target; + } + + public override int PixelWidth => _target.PixelSize.Width; + public override int PixelHeight => _target.PixelSize.Height; + + public static D2DRenderTargetBitmapImpl CreateCompatible( + ImagingFactory imagingFactory, + DirectWriteFactory dwriteFactory, + SharpDX.Direct2D1.RenderTarget renderTarget, + int pixelWidth, + int pixelHeight) + { + var bitmapRenderTarget = new BitmapRenderTarget( + renderTarget, + CompatibleRenderTargetOptions.None, + new Size2F(pixelWidth, pixelHeight)); + return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget); + } + + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + return new DrawingContextImpl( + visualBrushRenderer, + _target, + _dwriteFactory, + WicImagingFactory); + } + + public IRenderTargetBitmapImpl CreateLayer(int pixelWidth, int pixelHeight) + { + return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, pixelWidth, pixelHeight); + } + + public override void Dispose() + { + _target.Dispose(); + } + + public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) + { + return new OptionalDispose(_target.Bitmap, false); + } + } +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index e817dd4812..9b99b4c40a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -3,11 +3,10 @@ using System; using System.IO; -using Avalonia.Platform; using Avalonia.Win32.Interop; -using PixelFormat = SharpDX.WIC.PixelFormat; -using APixelFormat = Avalonia.Platform.PixelFormat; using SharpDX.WIC; +using APixelFormat = Avalonia.Platform.PixelFormat; +using D2DBitmap = SharpDX.Direct2D1.Bitmap; namespace Avalonia.Direct2D1.Media { @@ -16,18 +15,14 @@ namespace Avalonia.Direct2D1.Media /// public class WicBitmapImpl : BitmapImpl { - private readonly ImagingFactory _factory; - - /// /// Initializes a new instance of the class. /// /// The WIC imaging factory to use. /// The filename of the bitmap to load. public WicBitmapImpl(ImagingFactory factory, string fileName) + : base(factory) { - _factory = factory; - using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand)) { WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand); @@ -40,9 +35,8 @@ namespace Avalonia.Direct2D1.Media /// The WIC imaging factory to use. /// The stream to read the bitmap from. public WicBitmapImpl(ImagingFactory factory, Stream stream) + : base(factory) { - _factory = factory; - using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad)) { WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); @@ -57,11 +51,11 @@ namespace Avalonia.Direct2D1.Media /// The height of the bitmap. /// Pixel format public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null) + : base(factory) { if (!pixelFormat.HasValue) pixelFormat = APixelFormat.Bgra8888; - _factory = factory; PixelFormat = pixelFormat; WicImpl = new Bitmap( factory, @@ -71,7 +65,8 @@ namespace Avalonia.Direct2D1.Media BitmapCreateCacheOption.CacheOnLoad); } - public WicBitmapImpl(ImagingFactory factory, Platform.PixelFormat format, IntPtr data, int width, int height, int stride) + public WicBitmapImpl(ImagingFactory factory, APixelFormat format, IntPtr data, int width, int height, int stride) + : base(factory) { WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); PixelFormat = format; @@ -112,41 +107,23 @@ namespace Avalonia.Direct2D1.Media /// /// The render target. /// The Direct2D bitmap. - public override SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget) + public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget) { - FormatConverter converter = new FormatConverter(_factory); + FormatConverter converter = new FormatConverter(WicImagingFactory); converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA); - return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter); + return new OptionalDispose(D2DBitmap.FromWicBitmap(renderTarget, converter), true); } - /// - /// Saves the bitmap to a file. - /// - /// The filename. - public override void Save(string fileName) + public override void Save(Stream stream) { - 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)) + using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream)) + using (var frame = new BitmapFrameEncode(encoder)) { - Save(s); + frame.Initialize(); + frame.WriteSource(WicImpl); + frame.Commit(); + encoder.Commit(); } } - - public override void Save(Stream stream) - { - PngBitmapEncoder encoder = new PngBitmapEncoder(_factory); - encoder.Initialize(stream); - - BitmapFrameEncode frame = new BitmapFrameEncode(encoder); - frame.Initialize(); - frame.WriteSource(WicImpl); - frame.Commit(); - encoder.Commit(); - } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs similarity index 89% rename from src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs rename to src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs index 33736b02cb..1540ee333a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs @@ -10,12 +10,12 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1.Media { - public class RenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl + public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl { private readonly DirectWriteFactory _dwriteFactory; private readonly WicRenderTarget _target; - public RenderTargetBitmapImpl( + public WicRenderTargetBitmapImpl( ImagingFactory imagingFactory, Factory d2dFactory, DirectWriteFactory dwriteFactory, @@ -47,7 +47,7 @@ namespace Avalonia.Direct2D1.Media public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory); + return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory, WicImagingFactory); } } } diff --git a/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs b/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs new file mode 100644 index 0000000000..cd3eee8d25 --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs @@ -0,0 +1,22 @@ +using System; + +namespace Avalonia.Direct2D1 +{ + public struct OptionalDispose : IDisposable where T : IDisposable + { + private readonly bool _dispose; + + public OptionalDispose(T value, bool dispose) + { + Value = value; + _dispose = dispose; + } + + public T Value { get; } + + public void Dispose() + { + if (_dispose) Value?.Dispose(); + } + } +} diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index b4c9b49e3f..cbeff8b2f1 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -7,6 +7,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using SharpDX.Direct2D1; using DwFactory = SharpDX.DirectWrite.Factory; +using WicFactory = SharpDX.WIC.ImagingFactory; namespace Avalonia.Direct2D1 { @@ -25,24 +26,13 @@ namespace Avalonia.Direct2D1 { Direct2DFactory = AvaloniaLocator.Current.GetService(); DirectWriteFactory = AvaloniaLocator.Current.GetService(); + WicFactory = AvaloniaLocator.Current.GetService(); _renderTarget = renderTarget; } - /// - /// Gets the Direct2D factory. - /// - public Factory Direct2DFactory - { - get; - } - - /// - /// Gets the DirectWrite factory. - /// - public DwFactory DirectWriteFactory - { - get; - } + public Factory Direct2DFactory { get; } + public DwFactory DirectWriteFactory { get; } + public WicFactory WicFactory { get; } /// /// Creates a drawing context for a rendering session. @@ -50,7 +40,7 @@ namespace Avalonia.Direct2D1 /// An . public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, _renderTarget, DirectWriteFactory); + return new DrawingContextImpl(visualBrushRenderer, _renderTarget, DirectWriteFactory, WicFactory); } public void Dispose() diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 3c1024e73a..bd6555159c 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -10,10 +10,11 @@ using Factory = SharpDX.Direct2D1.Factory; using Factory2 = SharpDX.DXGI.Factory2; using Avalonia.Rendering; using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media.Imaging; namespace Avalonia.Direct2D1 { - public abstract class SwapChainRenderTarget : IRenderTarget + public abstract class SwapChainRenderTarget : IRenderTarget, ICreateLayer { private Size2 _savedSize; private Size2F _savedDpi; @@ -26,24 +27,12 @@ namespace Avalonia.Direct2D1 D2DDevice = AvaloniaLocator.Current.GetService(); Direct2DFactory = AvaloniaLocator.Current.GetService(); DirectWriteFactory = AvaloniaLocator.Current.GetService(); + WicImagingFactory = AvaloniaLocator.Current.GetService(); } - - /// - /// Gets the Direct2D factory. - /// - public Factory Direct2DFactory - { - get; - } - - /// - /// Gets the DirectWrite factory. - /// - public SharpDX.DirectWrite.Factory DirectWriteFactory - { - get; - } + public Factory Direct2DFactory { get; } + public SharpDX.DirectWrite.Factory DirectWriteFactory { get; } + public SharpDX.WIC.ImagingFactory WicImagingFactory { get; } protected SharpDX.DXGI.Device DxgiDevice { get; } @@ -69,9 +58,25 @@ namespace Avalonia.Direct2D1 visualBrushRenderer, _deviceContext, DirectWriteFactory, + WicImagingFactory, _swapChain); } + public IRenderTargetBitmapImpl CreateLayer(int pixelWidth, int pixelHeight) + { + if (_deviceContext == null) + { + CreateSwapChain(); + } + + return D2DRenderTargetBitmapImpl.CreateCompatible( + WicImagingFactory, + DirectWriteFactory, + _deviceContext, + pixelWidth, + pixelHeight); + } + public void Dispose() { _deviceContext?.Dispose(); @@ -86,7 +91,6 @@ namespace Avalonia.Direct2D1 _deviceContext?.Dispose(); _deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; - var swapChainDesc = new SwapChainDescription1 { Width = _savedSize.Width, From 8efd5e56a25ea3cf2a65e2d7f79a6697b9e13bd7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 29 Aug 2017 01:17:35 +0200 Subject: [PATCH 015/482] Added missing doc comments. --- .../Platform/IDrawingContextImpl.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 14aef8463a..1bae88cb5f 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -86,6 +86,9 @@ namespace Avalonia.Platform /// The clip rectangle. void PushClip(Rect clip); + /// + /// Pops the latest pushed clip rectangle. + /// void PopClip(); /// @@ -94,10 +97,19 @@ namespace Avalonia.Platform /// The opacity. void PushOpacity(double opacity); + /// + /// Pops the latest pushed opacity value. + /// void PopOpacity(); + /// + /// Pushes an opacity mask + /// void PushOpacityMask(IBrush mask, Rect bounds); + /// + /// Pops the latest pushed opacity mask. + /// void PopOpacityMask(); /// @@ -106,6 +118,9 @@ namespace Avalonia.Platform /// The clip geometry. void PushGeometryClip(IGeometryImpl clip); + /// + /// Pops the latest pushed geometry clip. + /// void PopGeometryClip(); } } From 5607404e6cb6a3e9c7d5e6e823668d42a807b3b2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 29 Aug 2017 13:14:19 +0200 Subject: [PATCH 016/482] Added IDrawingContextImpl.CreateLayer --- .../Direct3DInteropSample/MainWindow.cs | 2 +- .../Platform/IDrawingContextImpl.cs | 14 ++++++++++ .../SceneGraph/DeferredDrawingContextImpl.cs | 5 ++++ .../Avalonia.Cairo/Media/DrawingContext.cs | 6 +++++ src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 6 +++++ .../ExternalRenderTarget.cs | 15 +++++++++-- .../Avalonia.Direct2D1/ICreateLayer.cs | 10 ------- .../Avalonia.Direct2D1/ILayerFactory.cs | 10 +++++++ .../Media/DrawingContextImpl.cs | 26 +++++++++++++++++++ .../Imaging/D2DRenderTargetBitmapImpl.cs | 12 ++++----- .../Imaging/WicRenderTargetBitmapImpl.cs | 7 ++++- .../Avalonia.Direct2D1/RenderTarget.cs | 14 ++++++++-- .../SwapChainRenderTarget.cs | 8 +++--- 13 files changed, 109 insertions(+), 26 deletions(-) delete mode 100644 src/Windows/Avalonia.Direct2D1/ICreateLayer.cs create mode 100644 src/Windows/Avalonia.Direct2D1/ILayerFactory.cs diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs index 3fe3b896f6..ffa0de0a36 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -253,7 +253,7 @@ namespace Direct3DInteropSample public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, _window._d2dRenderTarget, + return new DrawingContextImpl(visualBrushRenderer, null, _window._d2dRenderTarget, AvaloniaLocator.Current.GetService(), AvaloniaLocator.Current.GetService()); } diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 1bae88cb5f..3db4527bfb 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -80,6 +80,20 @@ namespace Avalonia.Platform /// The corner radius. void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f); + /// + /// Creates a new that can be used as a render layer + /// for the current render target. + /// + /// The size of the layer in DIPs. + /// An + /// + /// Depending on the rendering backend used, a layer created via this method may be more + /// performant than a standard render target bitmap. In particular the Direct2D backend + /// has to do a format conversion each time a standard render target bitmap is rendered, + /// but a layer created via this method has no such overhead. + /// + IRenderTargetBitmapImpl CreateLayer(Size size); + /// /// Pushes a clip rectange. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 0b01960d5b..5fcd0e42bd 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -194,6 +194,11 @@ namespace Avalonia.Rendering.SceneGraph } } + public IRenderTargetBitmapImpl CreateLayer(Size size) + { + throw new NotImplementedException(); + } + /// public void PopClip() { diff --git a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs index 99b0a2ec73..7900f2e440 100644 --- a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs +++ b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs @@ -227,6 +227,12 @@ namespace Avalonia.Cairo.Media } } + public IRenderTargetBitmapImpl CreateLayer(Size size) + { + var surface = new Cairo.ImageSurface(Cairo.Format.Argb32, (int)size.Width, (int)size.Height); + return new RenderTargetBitmapImpl(surface); + } + /// /// Pushes a clip rectange. /// diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 7a83835b10..f01e9858e7 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -349,6 +349,12 @@ namespace Avalonia.Skia } } + public IRenderTargetBitmapImpl CreateLayer(Size size) + { + var pixelSize = size * (_dpi / 96); + return new BitmapImpl((int)pixelSize.Width, (int)pixelSize.Height, _dpi); + } + public void PushClip(Rect clip) { Canvas.Save(); diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs index d735c5f8bb..176cedd377 100644 --- a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; @@ -7,7 +8,7 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1 { - class ExternalRenderTarget : IRenderTarget + class ExternalRenderTarget : IRenderTarget, ILayerFactory { private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider; private readonly DirectWriteFactory _dwFactory; @@ -32,7 +33,7 @@ namespace Avalonia.Direct2D1 { var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); _externalRenderTargetProvider.BeforeDrawing(); - return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, _wicFactory, null, () => + return new DrawingContextImpl(visualBrushRenderer, null, target, _dwFactory, _wicFactory, null, () => { try { @@ -44,5 +45,15 @@ namespace Avalonia.Direct2D1 } }); } + + public IRenderTargetBitmapImpl CreateLayer(Size size) + { + var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); + return D2DRenderTargetBitmapImpl.CreateCompatible( + _wicFactory, + _dwFactory, + target, + size); + } } } diff --git a/src/Windows/Avalonia.Direct2D1/ICreateLayer.cs b/src/Windows/Avalonia.Direct2D1/ICreateLayer.cs deleted file mode 100644 index 9214377e6f..0000000000 --- a/src/Windows/Avalonia.Direct2D1/ICreateLayer.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using Avalonia.Platform; - -namespace Avalonia.Direct2D1 -{ - internal interface ICreateLayer - { - IRenderTargetBitmapImpl CreateLayer(int pixelWidth, int pixelHeight); - } -} diff --git a/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs b/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs new file mode 100644 index 0000000000..99f8d4f7ac --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/ILayerFactory.cs @@ -0,0 +1,10 @@ +using System; +using Avalonia.Platform; + +namespace Avalonia.Direct2D1 +{ + public interface ILayerFactory + { + IRenderTargetBitmapImpl CreateLayer(Size size); + } +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index a7bb73e19f..6a72923ce3 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -18,6 +18,7 @@ namespace Avalonia.Direct2D1.Media public class DrawingContextImpl : IDrawingContextImpl, IDisposable { private readonly IVisualBrushRenderer _visualBrushRenderer; + private readonly ILayerFactory _layerFactory; private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly Action _finishedCallback; @@ -29,12 +30,17 @@ namespace Avalonia.Direct2D1.Media /// /// The visual brush renderer. /// The render target to draw to. + /// + /// An object to use to create layers. May be null, in which case a + /// will created when a new layer is requested. + /// /// The DirectWrite factory. /// The WIC imaging factory. /// An optional swap chain associated with this drawing context. /// An optional delegate to be called when context is disposed. public DrawingContextImpl( IVisualBrushRenderer visualBrushRenderer, + ILayerFactory layerFactory, SharpDX.Direct2D1.RenderTarget renderTarget, SharpDX.DirectWrite.Factory directWriteFactory, SharpDX.WIC.ImagingFactory imagingFactory, @@ -42,6 +48,7 @@ namespace Avalonia.Direct2D1.Media Action finishedCallback = null) { _visualBrushRenderer = visualBrushRenderer; + _layerFactory = layerFactory; _renderTarget = renderTarget; _swapChain = swapChain; _finishedCallback = finishedCallback; @@ -285,6 +292,25 @@ namespace Avalonia.Direct2D1.Media } } + public IRenderTargetBitmapImpl CreateLayer(Size size) + { + if (_layerFactory != null) + { + return _layerFactory.CreateLayer(size); + } + else + { + var platform = AvaloniaLocator.Current.GetService(); + var dpi = new Vector(_renderTarget.DotsPerInch.Width, _renderTarget.DotsPerInch.Height); + var pixelSize = size * (dpi / 96); + return platform.CreateRenderTargetBitmap( + (int)pixelSize.Width, + (int)pixelSize.Height, + dpi.X, + dpi.Y); + } + } + /// /// Pushes a clip rectange. /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 6162d57bad..2843848fac 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -9,7 +9,7 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1.Media.Imaging { - public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ICreateLayer + public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory { private readonly DirectWriteFactory _dwriteFactory; private readonly BitmapRenderTarget _target; @@ -31,13 +31,12 @@ namespace Avalonia.Direct2D1.Media.Imaging ImagingFactory imagingFactory, DirectWriteFactory dwriteFactory, SharpDX.Direct2D1.RenderTarget renderTarget, - int pixelWidth, - int pixelHeight) + Size size) { var bitmapRenderTarget = new BitmapRenderTarget( renderTarget, CompatibleRenderTargetOptions.None, - new Size2F(pixelWidth, pixelHeight)); + new Size2F((float)size.Width, (float)size.Height)); return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget); } @@ -45,14 +44,15 @@ namespace Avalonia.Direct2D1.Media.Imaging { return new DrawingContextImpl( visualBrushRenderer, + this, _target, _dwriteFactory, WicImagingFactory); } - public IRenderTargetBitmapImpl CreateLayer(int pixelWidth, int pixelHeight) + public IRenderTargetBitmapImpl CreateLayer(Size size) { - return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, pixelWidth, pixelHeight); + return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, size); } public override void Dispose() diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs index 1540ee333a..f1065370b5 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs @@ -47,7 +47,12 @@ namespace Avalonia.Direct2D1.Media public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory, WicImagingFactory); + return new DrawingContextImpl( + visualBrushRenderer, + null, + _target, + _dwriteFactory, + WicImagingFactory); } } } diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index cbeff8b2f1..6086b0c67c 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -3,6 +3,7 @@ using System; using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX.Direct2D1; @@ -11,7 +12,7 @@ using WicFactory = SharpDX.WIC.ImagingFactory; namespace Avalonia.Direct2D1 { - public class RenderTarget : IRenderTarget + public class RenderTarget : IRenderTarget, ILayerFactory { /// /// The render target. @@ -40,7 +41,16 @@ namespace Avalonia.Direct2D1 /// An . public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, _renderTarget, DirectWriteFactory, WicFactory); + return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, DirectWriteFactory, WicFactory); + } + + public IRenderTargetBitmapImpl CreateLayer(Size size) + { + return D2DRenderTargetBitmapImpl.CreateCompatible( + WicFactory, + DirectWriteFactory, + _renderTarget, + size); } public void Dispose() diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index bd6555159c..0a23c63498 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -14,7 +14,7 @@ using Avalonia.Direct2D1.Media.Imaging; namespace Avalonia.Direct2D1 { - public abstract class SwapChainRenderTarget : IRenderTarget, ICreateLayer + public abstract class SwapChainRenderTarget : IRenderTarget, ILayerFactory { private Size2 _savedSize; private Size2F _savedDpi; @@ -56,13 +56,14 @@ namespace Avalonia.Direct2D1 return new DrawingContextImpl( visualBrushRenderer, + this, _deviceContext, DirectWriteFactory, WicImagingFactory, _swapChain); } - public IRenderTargetBitmapImpl CreateLayer(int pixelWidth, int pixelHeight) + public IRenderTargetBitmapImpl CreateLayer(Size size) { if (_deviceContext == null) { @@ -73,8 +74,7 @@ namespace Avalonia.Direct2D1 WicImagingFactory, DirectWriteFactory, _deviceContext, - pixelWidth, - pixelHeight); + size); } public void Dispose() From 9a1348b37b87a30048ddb197c5c2f3d6545bfe6e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 24 Sep 2017 00:37:11 +0300 Subject: [PATCH 017/482] Initial remote XAML previewer implementation --- Avalonia.sln | 92 +++++++++- Avalonia.sln.DotSettings | 1 + samples/ControlCatalog.NetCore/Program.cs | 22 ++- samples/Previewer/App.xaml | 6 + samples/Previewer/App.xaml.cs | 14 ++ samples/Previewer/Center.cs | 19 ++ samples/Previewer/MainWindow.xaml | 12 ++ samples/Previewer/MainWindow.xaml.cs | 86 +++++++++ samples/Previewer/Previewer.csproj | 27 +++ samples/Previewer/Program.cs | 13 ++ samples/RemoteTest/RemoteTest.csproj | 2 +- src/Avalonia.Controls/Design.cs | 2 +- .../InternalPlatformThreadingInterface.cs} | 14 +- src/Avalonia.Controls/Remote/RemoteWidget.cs | 2 +- .../Remote/Server/RemoteServerTopLevelImpl.cs | 10 +- .../DesignWindowLoader.cs | 76 ++++++++ .../DesignerAssist.cs | 60 +----- .../Avalonia.DotNetFrameworkRuntime.csproj | 4 +- .../BsonStreamTransport.cs | 19 +- .../DesignMessages.cs | 17 ++ src/Avalonia.Remote.Protocol/EventStash.cs | 20 +- src/Avalonia.Remote.Protocol/ITransport.cs | 4 +- .../TcpTransportBase.cs | 16 +- .../TransportConnectionWrapper.cs | 12 +- .../ViewportMessages.cs | 7 + .../Avalonia.LinuxFramebuffer.csproj | 24 +-- .../LinuxFramebufferPlatform.cs | 7 +- src/Linux/Avalonia.LinuxFramebuffer/Mice.cs | 2 +- .../Avalonia.Designer.HostApp.csproj | 25 +++ .../DetachableTransportConnection.cs | 35 ++++ .../PreviewerWindowImpl.cs | 91 +++++++++ .../PreviewerWindowingPlatform.cs | 66 +++++++ .../Avalonia.Designer.HostApp/Program.cs | 173 ++++++++++++++++++ src/tools/Avalonia.Designer.HostApp/Stubs.cs | 136 ++++++++++++++ 34 files changed, 977 insertions(+), 139 deletions(-) create mode 100644 samples/Previewer/App.xaml create mode 100644 samples/Previewer/App.xaml.cs create mode 100644 samples/Previewer/Center.cs create mode 100644 samples/Previewer/MainWindow.xaml create mode 100644 samples/Previewer/MainWindow.xaml.cs create mode 100644 samples/Previewer/Previewer.csproj create mode 100644 samples/Previewer/Program.cs rename src/{Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs => Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs} (89%) create mode 100644 src/Avalonia.DesignerSupport/DesignWindowLoader.cs create mode 100644 src/Avalonia.Remote.Protocol/DesignMessages.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj create mode 100644 src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/Program.cs create mode 100644 src/tools/Avalonia.Designer.HostApp/Stubs.cs diff --git a/Avalonia.sln b/Avalonia.sln index 56c489fd0b..2f2b175fa3 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 @@ -189,6 +189,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Remote.Protocol", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteTest", "samples\RemoteTest\RemoteTest.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{4ED8B739-6F4E-4CD4-B993-545E6B5CE637}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer.HostApp", "src\tools\Avalonia.Designer.HostApp\Avalonia.Designer.HostApp.csproj", "{050CC912-FF49-4A8B-B534-9544017446DD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Previewer", "samples\Previewer\Previewer.csproj", "{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -2598,6 +2604,86 @@ Global {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Mono.Build.0 = Release|Any CPU {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|x86.ActiveCfg = Release|Any CPU {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|x86.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.ActiveCfg = Ad-Hoc|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.Build.0 = Ad-Hoc|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.Build.0 = Ad-Hoc|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.Build.0 = AppStore|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.ActiveCfg = AppStore|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.Build.0 = AppStore|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.ActiveCfg = AppStore|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.Build.0 = AppStore|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.ActiveCfg = AppStore|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.Build.0 = AppStore|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.ActiveCfg = Debug|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.Build.0 = Debug|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.ActiveCfg = Debug|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.Build.0 = Debug|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.ActiveCfg = Debug|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.Build.0 = Debug|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.ActiveCfg = Release|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.Build.0 = Release|iPhone + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.ActiveCfg = Release|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.Build.0 = Release|Mono + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.ActiveCfg = Release|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.Build.0 = Release|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.ActiveCfg = Ad-Hoc|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.Build.0 = Ad-Hoc|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.Build.0 = Ad-Hoc|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.Build.0 = AppStore|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.ActiveCfg = AppStore|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.Build.0 = AppStore|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.ActiveCfg = AppStore|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.Build.0 = AppStore|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.ActiveCfg = AppStore|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.Build.0 = AppStore|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.ActiveCfg = Debug|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.Build.0 = Debug|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.ActiveCfg = Debug|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.Build.0 = Debug|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.ActiveCfg = Debug|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.Build.0 = Debug|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Any CPU.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.ActiveCfg = Release|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.Build.0 = Release|iPhone + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.ActiveCfg = Release|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.Build.0 = Release|Mono + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.ActiveCfg = Release|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2656,8 +2742,12 @@ Global {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888} {E1582370-37B3-403C-917F-8209551B1634} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1E8CA5AA-707A-4C57-A682-D265A49E10C3} + {050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection EndGlobal diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings index 1fd6f8d092..d61208c358 100644 --- a/Avalonia.sln.DotSettings +++ b/Avalonia.sln.DotSettings @@ -1,4 +1,5 @@  + True ExplicitlyExcluded ExplicitlyExcluded ExplicitlyExcluded diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 7c67a239e6..7e7fb7d2bf 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Avalonia; +using Avalonia.Controls; namespace ControlCatalog.NetCore { @@ -8,17 +9,22 @@ namespace ControlCatalog.NetCore { static void Main(string[] args) { - if (args.Contains("--fbdev")) AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => - { - tl.Content = new MainView(); - System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); - }); + if (args.Contains("--fbdev")) + AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => + { + tl.Content = new MainView(); + System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); + }); else - AppBuilder.Configure() - .UsePlatformDetect() - .Start(); + BuildAvaloniaApp().Start(); } + /// + /// This method is needed for IDE previewer infrastructure + /// + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure().UsePlatformDetect(); + static void ConsoleSilencer() { Console.CursorVisible = false; diff --git a/samples/Previewer/App.xaml b/samples/Previewer/App.xaml new file mode 100644 index 0000000000..6bae1955af --- /dev/null +++ b/samples/Previewer/App.xaml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs new file mode 100644 index 0000000000..fffa987a27 --- /dev/null +++ b/samples/Previewer/App.xaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Markup.Xaml; + +namespace Previewer +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + } + +} \ No newline at end of file diff --git a/samples/Previewer/Center.cs b/samples/Previewer/Center.cs new file mode 100644 index 0000000000..7a28827d61 --- /dev/null +++ b/samples/Previewer/Center.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; + +namespace Previewer +{ + public class Center : Decorator + { + protected override Size ArrangeOverride(Size finalSize) + { + if (Child != null) + { + var desired = Child.DesiredSize; + Child.Arrange(new Rect((finalSize.Width - desired.Width) / 2, (finalSize.Height - desired.Height) / 2, + desired.Width, desired.Height)); + } + return finalSize; + } + } +} \ No newline at end of file diff --git a/samples/Previewer/MainWindow.xaml b/samples/Previewer/MainWindow.xaml new file mode 100644 index 0000000000..eb612303f2 --- /dev/null +++ b/samples/Previewer/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Previewer/MainWindow.xaml.cs b/samples/Previewer/MainWindow.xaml.cs new file mode 100644 index 0000000000..c72b1f7e55 --- /dev/null +++ b/samples/Previewer/MainWindow.xaml.cs @@ -0,0 +1,86 @@ +using System; +using System.Net; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Remote; +using Avalonia.Markup.Xaml; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Designer; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; + +namespace Previewer +{ + public class MainWindow : Window + { + private const string InitialXaml = @" + Hello world! + + "; + private IAvaloniaRemoteTransportConnection _connection; + private Control _errorsContainer; + private TextBlock _errors; + private RemoteWidget _remote; + + + public MainWindow() + { + this.InitializeComponent(); + var tb = this.FindControl("Xaml"); + tb.Text = InitialXaml; + var scroll = this.FindControl("Remote"); + var rem = new Center(); + scroll.Content = rem; + _errorsContainer = this.FindControl("ErrorsContainer"); + _errors = this.FindControl("Errors"); + tb.GetObservable(TextBox.TextProperty).Subscribe(text => _connection?.Send(new UpdateXamlMessage + { + Xaml = text + })); + new BsonTcpTransport().Listen(IPAddress.Loopback, 25000, t => + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (_connection != null) + { + _connection.Dispose(); + _connection.OnMessage -= OnMessage; + } + _connection = t; + rem.Child = _remote = new RemoteWidget(t); + t.Send(new UpdateXamlMessage + { + Xaml = tb.Text + }); + + t.OnMessage += OnMessage; + }); + }); + Title = "Listening on 127.0.0.1:25000"; + } + + private void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (transport != _connection) + return; + if (obj is UpdateXamlResultMessage result) + { + _errorsContainer.IsVisible = result.Error != null; + _errors.Text = result.Error ?? ""; + } + if (obj is RequestViewportResizeMessage resize) + { + _remote.Width = Math.Min(4096, Math.Max(resize.Width, 1)); + _remote.Height = Math.Min(4096, Math.Max(resize.Height, 1)); + } + }); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj new file mode 100644 index 0000000000..13e2418d67 --- /dev/null +++ b/samples/Previewer/Previewer.csproj @@ -0,0 +1,27 @@ + + + Exe + netcoreapp2.0 + + + + %(Filename) + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Previewer/Program.cs b/samples/Previewer/Program.cs new file mode 100644 index 0000000000..48363e27f2 --- /dev/null +++ b/samples/Previewer/Program.cs @@ -0,0 +1,13 @@ +using System; +using Avalonia; + +namespace Previewer +{ + class Program + { + static void Main(string[] args) + { + AppBuilder.Configure().UsePlatformDetect().Start(); + } + } +} \ No newline at end of file diff --git a/samples/RemoteTest/RemoteTest.csproj b/samples/RemoteTest/RemoteTest.csproj index 8682916503..2487c66e41 100644 --- a/samples/RemoteTest/RemoteTest.csproj +++ b/samples/RemoteTest/RemoteTest.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp1.1 + netcoreapp2.0 diff --git a/src/Avalonia.Controls/Design.cs b/src/Avalonia.Controls/Design.cs index a0b0d5d0ca..ce52891749 100644 --- a/src/Avalonia.Controls/Design.cs +++ b/src/Avalonia.Controls/Design.cs @@ -64,7 +64,7 @@ namespace Avalonia.Controls return rv; } - internal static void ApplyDesignerProperties(Control target, Control source) + public static void ApplyDesignModeProperties(Control target, Control source) { if (source.IsSet(WidthProperty)) target.Width = source.GetValue(WidthProperty); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs similarity index 89% rename from src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs rename to src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs index 9231649754..eebde51555 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs +++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs @@ -1,19 +1,16 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; -using System.Text; using System.Threading; using System.Threading.Tasks; using Avalonia.Platform; using Avalonia.Rendering; -namespace Avalonia.LinuxFramebuffer +namespace Avalonia.Controls.Platform { - class PlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop + public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop { - public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); - - public PlatformThreadingInterface() + public InternalPlatformThreadingInterface() { TlsCurrentThreadIsLoopThread = true; StartTimer(new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs())); @@ -36,7 +33,7 @@ namespace Avalonia.LinuxFramebuffer while (true) { Action item; - lock(_actions) + lock (_actions) if (_actions.Count == 0) break; else @@ -66,6 +63,7 @@ namespace Avalonia.LinuxFramebuffer _timer = timer; _handle = GCHandle.Alloc(_timer); } + public void Dispose() { _handle.Free(); @@ -109,4 +107,4 @@ namespace Avalonia.LinuxFramebuffer public event EventHandler Tick; } -} +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs index f4bcb37d48..c05aeaf970 100644 --- a/src/Avalonia.Controls/Remote/RemoteWidget.cs +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls.Remote public RemoteWidget(IAvaloniaRemoteTransportConnection connection) { _connection = connection; - _connection.OnMessage += msg => Dispatcher.UIThread.InvokeAsync(() => OnMessage(msg)); + _connection.OnMessage += (t, msg) => Dispatcher.UIThread.InvokeAsync(() => OnMessage(msg)); _connection.Send(new ClientSupportedPixelFormatsMessage { Formats = new[] diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index be97f7caaa..c2e6a200f9 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -36,7 +36,7 @@ namespace Avalonia.Controls.Remote.Server _transport.OnMessage += OnMessage; } - private void OnMessage(object obj) + protected virtual void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) { lock (_lock) { @@ -88,6 +88,12 @@ namespace Avalonia.Controls.Remote.Server } } + protected void SetDpi(Vector dpi) + { + _dpi = dpi; + RenderIfNeeded(); + } + protected virtual Size Measure(Size constaint) { var l = (ILayoutable) InputRoot; @@ -131,7 +137,7 @@ namespace Avalonia.Controls.Remote.Server return _framebuffer; } - void RenderIfNeeded() + protected void RenderIfNeeded() { lock (_lock) { diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs new file mode 100644 index 0000000000..9ad2a216b4 --- /dev/null +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; + +namespace Avalonia.DesignerSupport +{ + public class DesignWindowLoader + { + public static Window LoadDesignerWindow(string xaml, string assemblyPath) + { + Window window; + Control control; + using (PlatformManager.DesignerMode()) + { + var loader = new AvaloniaXamlLoader(); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)); + + + + Uri baseUri = null; + if (assemblyPath != null) + { + //Fabricate fake Uri + baseUri = + new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(assemblyPath)); + } + + var loaded = loader.Load(stream, null, baseUri); + var styles = loaded as Styles; + if (styles != null) + { + var substitute = Design.GetPreviewWith(styles) ?? + styles.Select(Design.GetPreviewWith).FirstOrDefault(s => s != null); + if (substitute != null) + { + substitute.Styles.AddRange(styles); + control = substitute; + } + else + control = new StackPanel + { + Children = + { + new TextBlock {Text = "Styles can't be previewed without Design.PreviewWith. Add"}, + new TextBlock {Text = ""}, + new TextBlock {Text = " "}, + new TextBlock {Text = ""}, + new TextBlock {Text = "before setters in your first Style"} + } + }; + } + if (loaded is Application) + control = new TextBlock {Text = "Application can't be previewed in design view"}; + else + control = (Control) loaded; + + window = control as Window; + if (window == null) + { + window = new Window() {Content = (Control)control}; + } + + if (!window.IsSet(Window.SizeToContentProperty)) + window.SizeToContent = SizeToContent.WidthAndHeight; + } + window.Show(); + Design.ApplyDesignModeProperties(window, control); + return window; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.DesignerSupport/DesignerAssist.cs b/src/Avalonia.DesignerSupport/DesignerAssist.cs index 2f5fc79147..65c4c14d83 100644 --- a/src/Avalonia.DesignerSupport/DesignerAssist.cs +++ b/src/Avalonia.DesignerSupport/DesignerAssist.cs @@ -86,67 +86,11 @@ namespace Avalonia.DesignerSupport private static void UpdateXaml2(Dictionary dic) { var xamlInfo = new DesignerApiXamlFileInfo(dic); - Window window; - Control control; - - using (PlatformManager.DesignerMode()) - { - var loader = new AvaloniaXamlLoader(); - var stream = new MemoryStream(Encoding.UTF8.GetBytes(xamlInfo.Xaml)); - - - - Uri baseUri = null; - if (xamlInfo.AssemblyPath != null) - { - //Fabricate fake Uri - baseUri = - new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(xamlInfo.AssemblyPath)); - } - - var loaded = loader.Load(stream, null, baseUri); - var styles = loaded as Styles; - if (styles != null) - { - var substitute = Design.GetPreviewWith(styles) ?? - styles.Select(Design.GetPreviewWith).FirstOrDefault(s => s != null); - if (substitute != null) - { - substitute.Styles.AddRange(styles); - control = substitute; - } - else - control = new StackPanel - { - Children = - { - new TextBlock {Text = "Styles can't be previewed without Design.PreviewWith. Add"}, - new TextBlock {Text = ""}, - new TextBlock {Text = " "}, - new TextBlock {Text = ""}, - new TextBlock {Text = "before setters in your first Style"} - } - }; - } - if (loaded is Application) - control = new TextBlock {Text = "Application can't be previewed in design view"}; - else - control = (Control) loaded; - - window = control as Window; - if (window == null) - { - window = new Window() {Content = (Control)control}; - } - - if (!window.IsSet(Window.SizeToContentProperty)) - window.SizeToContent = SizeToContent.WidthAndHeight; - } + Window window = DesignWindowLoader.LoadDesignerWindow(xamlInfo.Xaml, xamlInfo.AssemblyPath); s_currentWindow?.Close(); s_currentWindow = window; - window.Show(); - Design.ApplyDesignerProperties(window, control); + // ReSharper disable once PossibleNullReferenceException // Always not null at this point Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle); diff --git a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj index 181f5e3a1e..32171e8495 100644 --- a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj +++ b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj @@ -10,7 +10,7 @@ Properties\SharedAssemblyInfo.cs - + @@ -18,4 +18,4 @@ - \ No newline at end of file + diff --git a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs index 8c983c385d..82765f98f6 100644 --- a/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs +++ b/src/Avalonia.Remote.Protocol/BsonStreamTransport.cs @@ -8,7 +8,7 @@ using Newtonsoft.Json.Bson; namespace Avalonia.Remote.Protocol { - public class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection + class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection { private readonly IMessageTypeResolver _resolver; private readonly Stream _inputStream; @@ -79,17 +79,8 @@ namespace Avalonia.Remote.Protocol var guid = new Guid(guidBytes); var buffer = new byte[length]; await ReadExact(buffer).ConfigureAwait(false); - if (Environment.GetEnvironmentVariable("WTF") == "WTF") - { - - using (var f = System.IO.File.Create("/tmp/wtf2.bin")) - { - f.Write(infoBlock, 0, infoBlock.Length); - f.Write(buffer, 0, buffer.Length); - } - } var message = Serializer.Deserialize(new BsonReader(new MemoryStream(buffer)), _resolver.GetByGuid(guid)); - OnMessage?.Invoke(message); + OnMessage?.Invoke(this, message); } } catch (Exception e) @@ -150,11 +141,11 @@ namespace Avalonia.Remote.Protocol var cancel = e as OperationCanceledException; if (cancel?.CancellationToken == _cancel) return; - OnException?.Invoke(e); + OnException?.Invoke(this, e); } - public event Action OnMessage; - public event Action OnException; + public event Action OnMessage; + public event Action OnException; } } \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/DesignMessages.cs b/src/Avalonia.Remote.Protocol/DesignMessages.cs new file mode 100644 index 0000000000..d6e898f320 --- /dev/null +++ b/src/Avalonia.Remote.Protocol/DesignMessages.cs @@ -0,0 +1,17 @@ +namespace Avalonia.Remote.Protocol.Designer +{ + [AvaloniaRemoteMessageGuid("9AEC9A2E-6315-4066-B4BA-E9A9EFD0F8CC")] + public class UpdateXamlMessage + { + public string Xaml { get; set; } + public string AssemblyPath { get; set; } + } + + [AvaloniaRemoteMessageGuid("B7A70093-0C5D-47FD-9261-22086D43A2E2")] + public class UpdateXamlResultMessage + { + public string Error { get; set; } + } + + +} \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/EventStash.cs b/src/Avalonia.Remote.Protocol/EventStash.cs index 06840c8253..1fe561d473 100644 --- a/src/Avalonia.Remote.Protocol/EventStash.cs +++ b/src/Avalonia.Remote.Protocol/EventStash.cs @@ -3,18 +3,20 @@ using System.Collections.Generic; namespace Avalonia.Remote.Protocol { - public class EventStash + class EventStash { + private readonly IAvaloniaRemoteTransportConnection _transport; private readonly Action _exceptionHandler; private List _stash; - private Action _delegate; + private Action _delegate; - public EventStash(Action exceptionHandler = null) + public EventStash(IAvaloniaRemoteTransportConnection transport, Action exceptionHandler = null) { + _transport = transport; _exceptionHandler = exceptionHandler; } - public void Add(Action handler) + public void Add(Action handler) { List stash; lock (this) @@ -37,25 +39,25 @@ namespace Avalonia.Remote.Protocol if (_exceptionHandler != null) try { - _delegate?.Invoke(m); + _delegate?.Invoke(_transport, m); } catch (Exception e) { _exceptionHandler(e); } else - _delegate?.Invoke(m); + _delegate?.Invoke(_transport, m); } } - public void Remove(Action handler) + public void Remove(Action handler) { lock (this) _delegate -= handler; } - public void Fire(T ev) + public void Fire(IAvaloniaRemoteTransportConnection transport, T ev) { if (_delegate == null) { @@ -66,7 +68,7 @@ namespace Avalonia.Remote.Protocol } } else - _delegate?.Invoke(ev); + _delegate?.Invoke(_transport, ev); } } } \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/ITransport.cs b/src/Avalonia.Remote.Protocol/ITransport.cs index d4e5427cb4..afc7d86845 100644 --- a/src/Avalonia.Remote.Protocol/ITransport.cs +++ b/src/Avalonia.Remote.Protocol/ITransport.cs @@ -9,7 +9,7 @@ namespace Avalonia.Remote.Protocol public interface IAvaloniaRemoteTransportConnection : IDisposable { Task Send(object data); - event Action OnMessage; - event Action OnException; + event Action OnMessage; + event Action OnException; } } diff --git a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs index 4278badaae..af62f2da59 100644 --- a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs +++ b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs @@ -50,17 +50,11 @@ namespace Avalonia.Remote.Protocol AcceptNew(); Task.Run(async () => { - try - { - var tcs = new TaskCompletionSource(); - var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0)); - cb(t); - await tcs.Task; - } - finally - { - cl.Dispose(); - } + var tcs = new TaskCompletionSource(); + var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0)); + cb(t); + await tcs.Task; + }); } diff --git a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs index b01b6d7303..1e821b7c24 100644 --- a/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs +++ b/src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs @@ -8,7 +8,7 @@ namespace Avalonia.Remote.Protocol { private readonly IAvaloniaRemoteTransportConnection _conn; private EventStash _onMessage; - private EventStash _onException = new EventStash(); + private EventStash _onException; private Queue _sendQueue = new Queue(); private object _lock =new object(); @@ -17,7 +17,8 @@ namespace Avalonia.Remote.Protocol public TransportConnectionWrapper(IAvaloniaRemoteTransportConnection conn) { _conn = conn; - _onMessage = new EventStash(_onException.Fire); + _onException = new EventStash(this); + _onMessage = new EventStash(this, e => _onException.Fire(this, e)); _conn.OnException +=_onException.Fire; conn.OnMessage += _onMessage.Fire; @@ -58,8 +59,7 @@ namespace Avalonia.Remote.Protocol { wi.Tcs.TrySetException(e); } - } - + } } public Task Send(object data) @@ -86,13 +86,13 @@ namespace Avalonia.Remote.Protocol return tcs.Task; } - public event Action OnMessage + public event Action OnMessage { add => _onMessage.Add(value); remove => _onMessage.Remove(value); } - public event Action OnException + public event Action OnException { add => _onException.Add(value); remove => _onException.Remove(value); diff --git a/src/Avalonia.Remote.Protocol/ViewportMessages.cs b/src/Avalonia.Remote.Protocol/ViewportMessages.cs index 82a7688f37..f71e893d26 100644 --- a/src/Avalonia.Remote.Protocol/ViewportMessages.cs +++ b/src/Avalonia.Remote.Protocol/ViewportMessages.cs @@ -30,6 +30,13 @@ namespace Avalonia.Remote.Protocol.Viewport public double DpiY { get; set; } } + [AvaloniaRemoteMessageGuid("9B47B3D8-61DF-4C38-ACD4-8C1BB72554AC")] + public class RequestViewportResizeMessage + { + public double Width { get; set; } + public double Height { get; set; } + } + [AvaloniaRemoteMessageGuid("63481025-7016-43FE-BADC-F2FD0F88609E")] public class ClientSupportedPixelFormatsMessage { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj index c38cb0f114..466b47f7a0 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj +++ b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj @@ -1,14 +1,14 @@  - - netstandard2.0 - true - - - - - - - - - + + netstandard2.0 + true + + + + + + + + + \ No newline at end of file diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index bc1684cc15..e733beae27 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -12,6 +12,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; using Avalonia; +using Avalonia.Controls.Platform; namespace Avalonia.LinuxFramebuffer { @@ -22,6 +23,7 @@ namespace Avalonia.LinuxFramebuffer public static MouseDevice MouseDevice = new MouseDevice(); private static readonly Stopwatch St = Stopwatch.StartNew(); internal static uint Timestamp => (uint)St.ElapsedTicks; + public static InternalPlatformThreadingInterface Threading; public static FramebufferToplevelImpl TopLevel; LinuxFramebufferPlatform(string fbdev = null) { @@ -31,12 +33,13 @@ namespace Avalonia.LinuxFramebuffer void Initialize() { + Threading = new InternalPlatformThreadingInterface(); AvaloniaLocator.CurrentMutable .Bind().ToTransient() .Bind().ToConstant(KeyboardDevice) .Bind().ToSingleton() - .Bind().ToConstant(PlatformThreadingInterface.Instance) - .Bind().ToConstant(PlatformThreadingInterface.Instance); + .Bind().ToConstant(Threading) + .Bind().ToConstant(Threading); } internal static TopLevel Initialize(T builder, string fbdev = null) where T : AppBuilderBase, new() diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs index d1cf9eefb6..7e85796c0c 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs @@ -55,7 +55,7 @@ namespace Avalonia.LinuxFramebuffer if (!ev.HasValue) break; - PlatformThreadingInterface.Instance.Send(() => ProcessEvent(dev, ev.Value)); + LinuxFramebufferPlatform.Threading.Send(() => ProcessEvent(dev, ev.Value)); } } } diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj new file mode 100644 index 0000000000..f0f3b12cc5 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -0,0 +1,25 @@ + + + Exe + netcoreapp2.0 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs b/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs new file mode 100644 index 0000000000..9cd87a2c59 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading.Tasks; +using Avalonia.Remote.Protocol; + +namespace Avalonia.Designer.HostApp +{ + class DetachableTransportConnection : IAvaloniaRemoteTransportConnection + { + private IAvaloniaRemoteTransportConnection _inner; + + public DetachableTransportConnection(IAvaloniaRemoteTransportConnection inner) + { + _inner = inner; + _inner.OnMessage += FireOnMessage; + } + + public void Dispose() + { + if (_inner != null) + _inner.OnMessage -= FireOnMessage; + _inner = null; + } + + public void FireOnMessage(IAvaloniaRemoteTransportConnection transport, object obj) => OnMessage?.Invoke(transport, obj); + + public Task Send(object data) + { + return _inner?.Send(data); + } + + public event Action OnMessage; + + public event Action OnException; + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs b/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs new file mode 100644 index 0000000000..5ffaa6459a --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs @@ -0,0 +1,91 @@ +using System; +using System.Reactive.Disposables; +using Avalonia.Controls; +using Avalonia.Controls.Remote.Server; +using Avalonia.Platform; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; + +namespace Avalonia.Designer.HostApp +{ + public class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl, IEmbeddableWindowImpl + { + private readonly IAvaloniaRemoteTransportConnection _transport; + + public PreviewerWindowImpl(IAvaloniaRemoteTransportConnection transport) : base(transport) + { + _transport = transport; + ClientSize = new Size(1, 1); + } + + public void Show() + { + } + + public void Hide() + { + } + + public void BeginMoveDrag() + { + } + + public void BeginResizeDrag(WindowEdge edge) + { + } + + public Point Position { get; set; } + public Action PositionChanged { get; set; } + public Action Deactivated { get; set; } + public Action Activated { get; set; } + public IPlatformHandle Handle { get; } + public WindowState WindowState { get; set; } + public Size MaxClientSize { get; } = new Size(4096, 4096); + public event Action LostFocus; + + protected override void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) + { + // In previewer mode we completely ignore client-side viewport size + if (obj is ClientViewportAllocatedMessage alloc) + { + Dispatcher.UIThread.InvokeAsync(() => SetDpi(new Vector(alloc.DpiX, alloc.DpiY))); + return; + } + base.OnMessage(transport, obj); + } + + public void Resize(Size clientSize) + { + _transport.Send(new RequestViewportResizeMessage + { + Width = clientSize.Width, + Height = clientSize.Height + }); + ClientSize = clientSize; + RenderIfNeeded(); + } + + public void Activate() + { + } + + public void SetTitle(string title) + { + } + + public IDisposable ShowDialog() + { + return Disposable.Empty; + } + + public void SetSystemDecorations(bool enabled) + { + } + + public void SetIcon(IWindowIconImpl icon) + { + } + + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs b/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs new file mode 100644 index 0000000000..b33f871139 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Platform; +using Avalonia.Remote.Protocol; +using Avalonia.Rendering; + +namespace Avalonia.Designer.HostApp +{ + class PreviewerWindowingPlatform : IWindowingPlatform, IPlatformSettings + { + static readonly IKeyboardDevice Keyboard = new KeyboardDevice(); + private static IAvaloniaRemoteTransportConnection s_transport; + private static DetachableTransportConnection s_lastWindowTransport; + private static PreviewerWindowImpl s_lastWindow; + public static List PreFlightMessages = new List(); + + public IWindowImpl CreateWindow() => new WindowStub(); + + public IEmbeddableWindowImpl CreateEmbeddableWindow() + { + if (s_lastWindow != null) + { + s_lastWindowTransport.Dispose(); + try + { + s_lastWindow.Dispose(); + } + catch + { + //Ignore + } + } + s_lastWindow = + new PreviewerWindowImpl(s_lastWindowTransport = new DetachableTransportConnection(s_transport)); + foreach (var pf in PreFlightMessages) + s_lastWindowTransport.FireOnMessage(s_lastWindowTransport, pf); + return s_lastWindow; + } + + public IPopupImpl CreatePopup() => new WindowStub(); + + public static void Initialize(IAvaloniaRemoteTransportConnection transport) + { + s_transport = transport; + var instance = new PreviewerWindowingPlatform(); + var threading = new InternalPlatformThreadingInterface(); + AvaloniaLocator.CurrentMutable + .Bind().ToSingleton() + .Bind().ToSingleton() + .Bind().ToConstant(Keyboard) + .Bind().ToConstant(instance) + .Bind().ToConstant(threading) + .Bind().ToConstant(threading) + .Bind().ToSingleton() + .Bind().ToConstant(instance) + .Bind().ToSingleton(); + + } + + public Size DoubleClickSize { get; } = new Size(2, 2); + public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/Program.cs b/src/tools/Avalonia.Designer.HostApp/Program.cs new file mode 100644 index 0000000000..88dadf479e --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/Program.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.DesignerSupport; +using Avalonia.Input; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Designer; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; + +namespace Avalonia.Designer.HostApp +{ + class Program + { + + private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats; + private static ClientViewportAllocatedMessage s_viewportAllocatedMessage; + private static IAvaloniaRemoteTransportConnection s_transport; + class CommandLineArgs + { + public string AppPath { get; set; } + public Uri Transport { get; set; } + } + + static Exception Die(string error) + { + if (error != null) + { + Console.Error.WriteLine(error); + Console.Error.Flush(); + } + Environment.Exit(1); + return new Exception("APPEXIT"); + } + + static Exception PrintUsage() + { + Console.Error.WriteLine("Usage: --transport transport_spec app"); + Console.Error.WriteLine(); + Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ MyApp.exe"); + Console.Error.Flush(); + return Die(null); + } + + static CommandLineArgs ParseCommandLineArgs(string[] args) + { + var rv = new CommandLineArgs(); + Action next = null; + try + { + foreach (var arg in args) + { + if (next != null) + { + next(arg); + next = null; + } + else if (arg == "--transport") + next = a => rv.Transport = new Uri(a, UriKind.Absolute); + else if (rv.AppPath == null) + rv.AppPath = arg; + else + PrintUsage(); + + } + if (rv.AppPath == null || rv.Transport == null) + PrintUsage(); + } + catch + { + PrintUsage(); + } + return rv; + } + + static IAvaloniaRemoteTransportConnection CreateTransport(Uri transport) + { + if (transport.Scheme == "tcp-bson") + { + return new BsonTcpTransport().Connect(IPAddress.Parse(transport.Host), transport.Port).Result; + } + PrintUsage(); + return null; + } + + interface IAppInitializer + { + Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj); + } + + class AppInitializer : IAppInitializer where T : AppBuilderBase, new() + { + public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj) + { + var builder = (AppBuilderBase) obj; + builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport)); + builder.SetupWithoutStarting(); + return builder.Instance; + } + } + + private const string BuilderMethodName = "BuildAvaloniaApp"; + + class NeverClose : ICloseable + { + public event EventHandler Closed; + } + + static void Main(string[] cmdline) + { + var args = ParseCommandLineArgs(cmdline); + var transport = CreateTransport(args.Transport); + var asm = Assembly.LoadFile(System.IO.Path.GetFullPath(args.AppPath)); + var entryPoint = asm.EntryPoint; + if (entryPoint == null) + throw Die($"Assembly {args.AppPath} doesn't have an entry point"); + var builderMethod = entryPoint.DeclaringType.GetMethod(BuilderMethodName, + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (builderMethod == null) + throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}"); + + var appBuilder = builderMethod.Invoke(null, null); + var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType())); + var app = initializer.GetConfiguredApp(transport, appBuilder); + s_transport = transport; + transport.OnMessage += OnTransportMessage; + transport.OnException += (t, e) => Die(e.ToString()); + app.Run(new NeverClose()); + } + + + private static void RebuildPreFlight() + { + PreviewerWindowingPlatform.PreFlightMessages = new List + { + s_supportedPixelFormats, + s_viewportAllocatedMessage + }; + } + + private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.InvokeAsync(() => + { + if (obj is ClientSupportedPixelFormatsMessage formats) + { + s_supportedPixelFormats = formats; + RebuildPreFlight(); + } + if (obj is ClientViewportAllocatedMessage viewport) + { + s_viewportAllocatedMessage = viewport; + RebuildPreFlight(); + } + if (obj is UpdateXamlMessage xaml) + { + try + { + DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath); + s_transport.Send(new UpdateXamlResultMessage()); + } + catch (Exception e) + { + s_transport.Send(new UpdateXamlResultMessage + { + Error = e.ToString() + }); + } + } + }); + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/Stubs.cs b/src/tools/Avalonia.Designer.HostApp/Stubs.cs new file mode 100644 index 0000000000..a4160e7da4 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp/Stubs.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reactive.Disposables; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Input.Raw; +using Avalonia.Platform; +using Avalonia.Rendering; + +namespace Avalonia.Designer.HostApp +{ + /// + /// Popups are no-op + /// + + class WindowStub : IPopupImpl, IWindowImpl + { + public Action Deactivated { get; set; } + public Action Activated { get; set; } + public IPlatformHandle Handle { get; } + public Size MaxClientSize { get; } + public Size ClientSize { get; } + public double Scaling { get; } + public IEnumerable Surfaces { get; } + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Action Closed { get; set; } + public IMouseDevice MouseDevice { get; } = new MouseDevice(); + public Point Position { get; set; } + public Action PositionChanged { get; set; } + public WindowState WindowState { get; set; } + public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root); + public void Dispose() + { + } + public void Invalidate(Rect rect) + { + } + + public void SetInputRoot(IInputRoot inputRoot) + { + } + + public Point PointToClient(Point point) => point; + + public Point PointToScreen(Point point) => point; + + public void SetCursor(IPlatformHandle cursor) + { + } + + public void Show() + { + } + + public void Hide() + { + } + + public void BeginMoveDrag() + { + } + + public void BeginResizeDrag(WindowEdge edge) + { + } + + public void Activate() + { + } + + public void Resize(Size clientSize) + { + } + + public void SetTitle(string title) + { + } + + public IDisposable ShowDialog() => Disposable.Empty; + + public void SetSystemDecorations(bool enabled) + { + } + + public void SetIcon(IWindowIconImpl icon) + { + } + } + + class ClipboardStub : IClipboard + { + public Task GetTextAsync() => Task.FromResult(""); + + public Task SetTextAsync(string text) => Task.CompletedTask; + + public Task ClearAsync() => Task.CompletedTask; + } + + class CursorFactoryStub : IStandardCursorFactory + { + public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB"); + } + + class IconLoaderStub : IPlatformIconLoader + { + class IconStub : IWindowIconImpl + { + public void Save(Stream outputStream) + { + + } + } + + public IWindowIconImpl LoadIcon(string fileName) => new IconStub(); + + public IWindowIconImpl LoadIcon(Stream stream) => new IconStub(); + + public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) => new IconStub(); + } + + class SystemDialogsStub : ISystemDialogImpl + { + public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) => + Task.FromResult((string[]) null); + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) => + Task.FromResult((string) null); + } +} \ No newline at end of file From bb5a7f0cd3d1157885f5f2e140142f5a0adbf843 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 23 Sep 2017 20:54:06 -0700 Subject: [PATCH 018/482] Removed IRenderLayerFactory And use `IDrawingContextImpl.CreateLayer` instead. --- .../Rendering/DefaultRenderLayerFactory.cs | 34 --- .../Rendering/DeferredRenderer.cs | 153 ++++++------ .../Rendering/IRenderLayerFactory.cs | 11 - src/Avalonia.Visuals/Rendering/RenderLayer.cs | 10 +- .../Rendering/RenderLayers.cs | 9 +- .../Rendering/DeferredRendererTests.cs | 220 ++++++++---------- 6 files changed, 175 insertions(+), 262 deletions(-) delete mode 100644 src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs delete mode 100644 src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs deleted file mode 100644 index c75f948b66..0000000000 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using Avalonia.Platform; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering -{ - public class DefaultRenderLayerFactory : IRenderLayerFactory - { - private IPlatformRenderInterface _renderInterface; - - public DefaultRenderLayerFactory() - : this(AvaloniaLocator.Current.GetService()) - { - } - - public DefaultRenderLayerFactory(IPlatformRenderInterface renderInterface) - { - _renderInterface = renderInterface; - } - - public IRenderTargetBitmapImpl CreateLayer( - IVisual layerRoot, - Size size, - double dpiX, - double dpiY) - { - return _renderInterface.CreateRenderTargetBitmap( - (int)Math.Ceiling(size.Width), - (int)Math.Ceiling(size.Height), - dpiX, - dpiY); - } - } -} diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index c30fb3bdc3..d42f2bd8a4 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -26,7 +26,6 @@ namespace Avalonia.Rendering private readonly IVisual _root; private readonly ISceneBuilder _sceneBuilder; private readonly RenderLayers _layers; - private readonly IRenderLayerFactory _layerFactory; private bool _running; private Scene _scene; @@ -45,13 +44,11 @@ namespace Avalonia.Rendering /// The control to render. /// The render loop. /// The scene builder to use. Optional. - /// The layer factory to use. Optional. /// The dispatcher to use. Optional. public DeferredRenderer( IRenderRoot root, IRenderLoop renderLoop, ISceneBuilder sceneBuilder = null, - IRenderLayerFactory layerFactory = null, IDispatcher dispatcher = null) { Contract.Requires(root != null); @@ -59,8 +56,7 @@ namespace Avalonia.Rendering _dispatcher = dispatcher ?? Dispatcher.UIThread; _root = root; _sceneBuilder = sceneBuilder ?? new SceneBuilder(); - _layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); - _layers = new RenderLayers(_layerFactory); + _layers = new RenderLayers(); _renderLoop = renderLoop; } @@ -70,15 +66,13 @@ namespace Avalonia.Rendering /// The control to render. /// The render target. /// The scene builder to use. Optional. - /// The layer factory to use. Optional. /// /// This constructor is intended to be used for unit testing. /// public DeferredRenderer( IVisual root, IRenderTarget renderTarget, - ISceneBuilder sceneBuilder = null, - IRenderLayerFactory layerFactory = null) + ISceneBuilder sceneBuilder = null) { Contract.Requires(root != null); Contract.Requires(renderTarget != null); @@ -86,8 +80,7 @@ namespace Avalonia.Rendering _root = root; _renderTarget = renderTarget; _sceneBuilder = sceneBuilder ?? new SceneBuilder(); - _layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); - _layers = new RenderLayers(_layerFactory); + _layers = new RenderLayers(); } /// @@ -180,38 +173,56 @@ namespace Avalonia.Rendering bool renderOverlay = DrawDirtyRects || DrawFps; bool composite = false; + if (_renderTarget == null) + { + _renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); + } + if (renderOverlay) { _dirtyRectsDisplay.Tick(); } - if (scene != null && scene.Size != Size.Empty) + try { - if (scene.Generation != _lastSceneId) + using (var context = _renderTarget.CreateDrawingContext(this)) { - _layers.Update(scene); - RenderToLayers(scene); - - if (DebugFramesPath != null) + if (scene != null && scene.Size != Size.Empty) { - SaveDebugFrames(scene.Generation); - } + if (scene.Generation != _lastSceneId) + { + _layers.Update(scene, context); - _lastSceneId = scene.Generation; + RenderToLayers(scene); - composite = true; - } + if (DebugFramesPath != null) + { + SaveDebugFrames(scene.Generation); + } - if (renderOverlay) - { - RenderOverlay(scene); - RenderComposite(scene); - } - else if(composite) - { - RenderComposite(scene); + _lastSceneId = scene.Generation; + + composite = true; + } + + if (renderOverlay) + { + RenderOverlay(scene, context); + RenderComposite(scene, context); + } + else if (composite) + { + RenderComposite(scene, context); + } + } } } + catch (RenderTargetCorruptedException ex) + { + Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); + _renderTarget?.Dispose(); + _renderTarget = null; + } } private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds) @@ -273,11 +284,11 @@ namespace Avalonia.Rendering } } - private void RenderOverlay(Scene scene) + private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent) { if (DrawDirtyRects) { - var overlay = GetOverlay(scene.Size, scene.Scaling); + var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling); using (var context = overlay.CreateDrawingContext(this)) { @@ -301,61 +312,44 @@ namespace Avalonia.Rendering } } - private void RenderComposite(Scene scene) + private void RenderComposite(Scene scene, IDrawingContextImpl context) { - try + var clientRect = new Rect(scene.Size); + + foreach (var layer in scene.Layers) { - if (_renderTarget == null) + var bitmap = _layers[layer.LayerRoot].Bitmap; + var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); + + if (layer.GeometryClip != null) { - _renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); + context.PushGeometryClip(layer.GeometryClip); } - using (var context = _renderTarget.CreateDrawingContext(this)) + if (layer.OpacityMask == null) { - var clientRect = new Rect(scene.Size); - - foreach (var layer in scene.Layers) - { - var bitmap = _layers[layer.LayerRoot].Bitmap; - var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); - - if (layer.GeometryClip != null) - { - context.PushGeometryClip(layer.GeometryClip); - } - - if (layer.OpacityMask == null) - { - context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect); - } - else - { - context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect); - } - - if (layer.GeometryClip != null) - { - context.PopGeometryClip(); - } - } - - if (_overlay != null) - { - var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight); - context.DrawImage(_overlay, 0.5, sourceRect, clientRect); - } + context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect); + } + else + { + context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect); + } - if (DrawFps) - { - RenderFps(context, clientRect, true); - } + if (layer.GeometryClip != null) + { + context.PopGeometryClip(); } } - catch (RenderTargetCorruptedException ex) + + if (_overlay != null) { - Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); - _renderTarget?.Dispose(); - _renderTarget = null; + var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight); + context.DrawImage(_overlay, 0.5, sourceRect, clientRect); + } + + if (DrawFps) + { + RenderFps(context, clientRect, true); } } @@ -422,7 +416,10 @@ namespace Avalonia.Rendering } } - private IRenderTargetBitmapImpl GetOverlay(Size size, double scaling) + private IRenderTargetBitmapImpl GetOverlay( + IDrawingContextImpl parentContext, + Size size, + double scaling) { size = new Size(size.Width * scaling, size.Height * scaling); @@ -431,7 +428,7 @@ namespace Avalonia.Rendering _overlay.PixelHeight != size.Height) { _overlay?.Dispose(); - _overlay = _layerFactory.CreateLayer(null, size, 96 * scaling, 96 * scaling); + _overlay = parentContext.CreateLayer(size); } return _overlay; diff --git a/src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs b/src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs deleted file mode 100644 index ed2751cd64..0000000000 --- a/src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using Avalonia.Platform; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering -{ - public interface IRenderLayerFactory - { - IRenderTargetBitmapImpl CreateLayer(IVisual layerRoot, Size size, double dpiX, double dpiY); - } -} diff --git a/src/Avalonia.Visuals/Rendering/RenderLayer.cs b/src/Avalonia.Visuals/Rendering/RenderLayer.cs index df9497af6f..ed33295db6 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayer.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayer.cs @@ -7,16 +7,16 @@ namespace Avalonia.Rendering { public class RenderLayer { - private readonly IRenderLayerFactory _factory; + private readonly IDrawingContextImpl _drawingContext; public RenderLayer( - IRenderLayerFactory factory, + IDrawingContextImpl drawingContext, Size size, double scaling, IVisual layerRoot) { - _factory = factory; - Bitmap = factory.CreateLayer(layerRoot, size * scaling, 96 * scaling, 96 * scaling); + _drawingContext = drawingContext; + Bitmap = drawingContext.CreateLayer(size); Size = size; Scaling = scaling; LayerRoot = layerRoot; @@ -31,7 +31,7 @@ namespace Avalonia.Rendering { if (Size != size || Scaling != scaling) { - var resized = _factory.CreateLayer(LayerRoot, size * scaling, 96 * scaling, 96 * scaling); + var resized = _drawingContext.CreateLayer(size); using (var context = resized.CreateDrawingContext(null)) { diff --git a/src/Avalonia.Visuals/Rendering/RenderLayers.cs b/src/Avalonia.Visuals/Rendering/RenderLayers.cs index e1b22c55e0..bafd644603 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayers.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayers.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Avalonia.VisualTree; @@ -8,19 +9,17 @@ namespace Avalonia.Rendering { public class RenderLayers : IEnumerable { - private readonly IRenderLayerFactory _factory; private List _inner = new List(); private Dictionary _index = new Dictionary(); - public RenderLayers(IRenderLayerFactory factory) + public RenderLayers() { - _factory = factory; } public int Count => _inner.Count; public RenderLayer this[IVisual layerRoot] => _index[layerRoot]; - public void Update(Scene scene) + public void Update(Scene scene, IDrawingContextImpl context) { for (var i = scene.Layers.Count - 1; i >= 0; --i) { @@ -29,7 +28,7 @@ namespace Avalonia.Rendering if (!_index.TryGetValue(src.LayerRoot, out layer)) { - layer = new RenderLayer(_factory, scene.Size, scene.Scaling, src.LayerRoot); + layer = new RenderLayer(context, scene.Size, scene.Scaling, src.LayerRoot); _inner.Add(layer); _index.Add(src.LayerRoot, layer); } diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index a9b27ed601..047cb05c26 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -30,7 +30,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering root, loop.Object, sceneBuilder: MockSceneBuilder(root).Object, - layerFactory: MockLayerFactory(root).Object, dispatcher: dispatcher.Object); target.Start(); @@ -55,7 +54,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder.Object, - layerFactory: MockLayerFactory(root).Object, dispatcher: dispatcher); target.Start(); @@ -75,7 +73,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder.Object, - layerFactory: MockLayerFactory(root).Object, dispatcher: dispatcher); target.Start(); @@ -111,7 +108,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder.Object, - layerFactory: MockLayerFactory(root).Object, dispatcher: dispatcher); target.Start(); @@ -133,100 +129,102 @@ namespace Avalonia.Visuals.UnitTests.Rendering [Fact] public void Frame_Should_Create_Layer_For_Root() { - var loop = new Mock(); - var root = new TestRoot(); - var rootLayer = new Mock(); - var dispatcher = new ImmediateDispatcher(); - - var sceneBuilder = new Mock(); - sceneBuilder.Setup(x => x.UpdateAll(It.IsAny())) - .Callback(scene => - { - scene.Size = root.ClientSize; - scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); - }); - - var layers = new Mock(); - layers.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer()); - - var renderInterface = new Mock(); - - var target = new DeferredRenderer( - root, - loop.Object, - sceneBuilder: sceneBuilder.Object, - layerFactory: layers.Object, - dispatcher: dispatcher); - - target.Start(); - RunFrame(loop); - - layers.Verify(x => x.CreateLayer(root, root.ClientSize, 96, 96)); + throw new NotImplementedException(); + //var loop = new Mock(); + //var root = new TestRoot(); + //var rootLayer = new Mock(); + //var dispatcher = new ImmediateDispatcher(); + + //var sceneBuilder = new Mock(); + //sceneBuilder.Setup(x => x.UpdateAll(It.IsAny())) + // .Callback(scene => + // { + // scene.Size = root.ClientSize; + // scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); + // }); + + //var layers = new Mock(); + //layers.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer()); + + //var renderInterface = new Mock(); + + //var target = new DeferredRenderer( + // root, + // loop.Object, + // sceneBuilder: sceneBuilder.Object, + // layerFactory: layers.Object, + // dispatcher: dispatcher); + + //target.Start(); + //RunFrame(loop); + + //layers.Verify(x => x.CreateLayer(root, root.ClientSize, 96, 96)); } [Fact] public void Should_Create_And_Delete_Layers_For_Transparent_Controls() { - Border border; - var root = new TestRoot - { - Width = 100, - Height = 100, - Child = new Border - { - Background = Brushes.Red, - Child = border = new Border - { - Background = Brushes.Green, - } - } - }; - - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var loop = new Mock(); - var layerFactory = new MockRenderLayerFactory(new Dictionary - { - { root, CreateLayer() }, - { border, CreateLayer() }, - }); - - var target = new DeferredRenderer( - root, - loop.Object, - layerFactory: layerFactory, - dispatcher: new ImmediateDispatcher()); - root.Renderer = target; - - target.Start(); - RunFrame(loop); - - var rootContext = layerFactory.GetMockDrawingContext(root); - var borderContext = layerFactory.GetMockDrawingContext(border); - - rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); - rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); - borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); - - rootContext.ResetCalls(); - borderContext.ResetCalls(); - border.Opacity = 0.5; - RunFrame(loop); - - rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); - rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Never); - borderContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); - - rootContext.ResetCalls(); - borderContext.ResetCalls(); - border.Opacity = 1; - RunFrame(loop); - - layerFactory.GetMockBitmap(border).Verify(x => x.Dispose()); - rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); - rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); - borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + throw new NotImplementedException(); + //Border border; + //var root = new TestRoot + //{ + // Width = 100, + // Height = 100, + // Child = new Border + // { + // Background = Brushes.Red, + // Child = border = new Border + // { + // Background = Brushes.Green, + // } + // } + //}; + + //root.Measure(Size.Infinity); + //root.Arrange(new Rect(root.DesiredSize)); + + //var loop = new Mock(); + //var layerFactory = new MockRenderLayerFactory(new Dictionary + //{ + // { root, CreateLayer() }, + // { border, CreateLayer() }, + //}); + + //var target = new DeferredRenderer( + // root, + // loop.Object, + // layerFactory: layerFactory, + // dispatcher: new ImmediateDispatcher()); + //root.Renderer = target; + + //target.Start(); + //RunFrame(loop); + + //var rootContext = layerFactory.GetMockDrawingContext(root); + //var borderContext = layerFactory.GetMockDrawingContext(border); + + //rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + //rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + //borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + + //rootContext.ResetCalls(); + //borderContext.ResetCalls(); + //border.Opacity = 0.5; + //RunFrame(loop); + + //rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + //rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Never); + //borderContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + + //rootContext.ResetCalls(); + //borderContext.ResetCalls(); + //border.Opacity = 1; + //RunFrame(loop); + + //layerFactory.GetMockBitmap(border).Verify(x => x.Dispose()); + //rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + //rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + //borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } private void IgnoreFirstFrame(Mock loop, Mock sceneBuilder) @@ -246,13 +244,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering x.CreateDrawingContext(It.IsAny()) == Mock.Of()); } - private Mock MockLayerFactory(IRenderRoot root) - { - var result = new Mock(); - result.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer()); - return result; - } - private Mock MockSceneBuilder(IRenderRoot root) { var result = new Mock(); @@ -260,34 +251,5 @@ namespace Avalonia.Visuals.UnitTests.Rendering .Callback(x => x.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize))); return result; } - - private class MockRenderLayerFactory : IRenderLayerFactory - { - private IDictionary _layers; - - public MockRenderLayerFactory(IDictionary layers) - { - _layers = layers; - } - - public IRenderTargetBitmapImpl CreateLayer( - IVisual layerRoot, - Size size, - double dpiX, - double dpiY) - { - return _layers[layerRoot]; - } - - public Mock GetMockBitmap(IVisual layerRoot) - { - return Mock.Get(_layers[layerRoot]); - } - - public Mock GetMockDrawingContext(IVisual layerRoot) - { - return Mock.Get(_layers[layerRoot].CreateDrawingContext(null)); - } - } } } From d141acab30da31254360613b12184180239fa6c4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 23 Sep 2017 20:59:41 -0700 Subject: [PATCH 019/482] Use correct size for overlay. --- src/Avalonia.Visuals/Rendering/DeferredRenderer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index d42f2bd8a4..81a2998fd2 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -421,11 +421,11 @@ namespace Avalonia.Rendering Size size, double scaling) { - size = new Size(size.Width * scaling, size.Height * scaling); + var pixelSize = size * scaling; if (_overlay == null || - _overlay.PixelWidth != size.Width || - _overlay.PixelHeight != size.Height) + _overlay.PixelWidth != pixelSize.Width || + _overlay.PixelHeight != pixelSize.Height) { _overlay?.Dispose(); _overlay = parentContext.CreateLayer(size); From b0d8ff69ea300769018181a6d604bbde1f4118dc Mon Sep 17 00:00:00 2001 From: Dmitriy Arndt Date: Tue, 26 Sep 2017 00:11:32 +0300 Subject: [PATCH 020/482] Fixed: MenuItem with sub-items is not closed when other menu item without sub-items is selected #226 --- src/Avalonia.Controls/MenuItem.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 926c240e57..dadd3b910b 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -307,6 +307,21 @@ namespace Avalonia.Controls () => IsSubMenuOpen = true, TimeSpan.FromMilliseconds(400)); } + else + { + var parentItem = Parent as MenuItem; + if (parentItem != null) + { + foreach (var sibling in parentItem.Items + .OfType() + .Where(x => x != this && x.IsSubMenuOpen)) + { + sibling.CloseSubmenus(); + sibling.IsSubMenuOpen = false; + sibling.IsSelected = false; + } + } + } } /// From 7c1245829d9361002cf5d9ba1e277f2964e04c81 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 26 Sep 2017 21:08:42 +0300 Subject: [PATCH 021/482] [GTK3] Timers should have lower priority than Signal() --- src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs index 1936d40086..07ca4a9539 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs @@ -55,7 +55,7 @@ namespace Avalonia.Gtk3.Interop public static IDisposable StarTimer(uint interval, Action tick) { var timer = new Timer (); - GlibTimeout.Add(interval, + GlibTimeout.Add(101, interval, () => { if (timer.Stopped) From d7e74229990205b1865586f44c65fb7571ba0d0e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 27 Sep 2017 13:37:58 +0300 Subject: [PATCH 022/482] Moved remote previewer entry point to DesignSupport.dll --- .../Remote}/DetachableTransportConnection.cs | 2 +- .../Remote}/PreviewerWindowImpl.cs | 4 +- .../Remote}/PreviewerWindowingPlatform.cs | 2 +- .../Remote/RemoteDesignerEntryPoint.cs | 172 ++++++++++++++++++ .../Remote}/Stubs.cs | 6 +- .../Avalonia.Designer.HostApp.csproj | 1 - .../Avalonia.Designer.HostApp/Program.cs | 169 +---------------- 7 files changed, 180 insertions(+), 176 deletions(-) rename src/{tools/Avalonia.Designer.HostApp => Avalonia.DesignerSupport/Remote}/DetachableTransportConnection.cs (95%) rename src/{tools/Avalonia.Designer.HostApp => Avalonia.DesignerSupport/Remote}/PreviewerWindowImpl.cs (94%) rename src/{tools/Avalonia.Designer.HostApp => Avalonia.DesignerSupport/Remote}/PreviewerWindowingPlatform.cs (98%) create mode 100644 src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs rename src/{tools/Avalonia.Designer.HostApp => Avalonia.DesignerSupport/Remote}/Stubs.cs (97%) diff --git a/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs b/src/Avalonia.DesignerSupport/Remote/DetachableTransportConnection.cs similarity index 95% rename from src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs rename to src/Avalonia.DesignerSupport/Remote/DetachableTransportConnection.cs index 9cd87a2c59..1fb10a3a7c 100644 --- a/src/tools/Avalonia.Designer.HostApp/DetachableTransportConnection.cs +++ b/src/Avalonia.DesignerSupport/Remote/DetachableTransportConnection.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using Avalonia.Remote.Protocol; -namespace Avalonia.Designer.HostApp +namespace Avalonia.DesignerSupport.Remote { class DetachableTransportConnection : IAvaloniaRemoteTransportConnection { diff --git a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs similarity index 94% rename from src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs rename to src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 5ffaa6459a..a787c8e8e3 100644 --- a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -7,9 +7,9 @@ using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Viewport; using Avalonia.Threading; -namespace Avalonia.Designer.HostApp +namespace Avalonia.DesignerSupport.Remote { - public class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl, IEmbeddableWindowImpl + class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl, IEmbeddableWindowImpl { private readonly IAvaloniaRemoteTransportConnection _transport; diff --git a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs similarity index 98% rename from src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs rename to src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs index b33f871139..ca3bb2b97f 100644 --- a/src/tools/Avalonia.Designer.HostApp/PreviewerWindowingPlatform.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs @@ -7,7 +7,7 @@ using Avalonia.Platform; using Avalonia.Remote.Protocol; using Avalonia.Rendering; -namespace Avalonia.Designer.HostApp +namespace Avalonia.DesignerSupport.Remote { class PreviewerWindowingPlatform : IWindowingPlatform, IPlatformSettings { diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs new file mode 100644 index 0000000000..ac3438d71c --- /dev/null +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.DesignerSupport; +using Avalonia.Input; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Designer; +using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Threading; + +namespace Avalonia.DesignerSupport.Remote +{ + public class RemoteDesignerEntryPoint + { + private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats; + private static ClientViewportAllocatedMessage s_viewportAllocatedMessage; + private static IAvaloniaRemoteTransportConnection s_transport; + class CommandLineArgs + { + public string AppPath { get; set; } + public Uri Transport { get; set; } + } + + static Exception Die(string error) + { + if (error != null) + { + Console.Error.WriteLine(error); + Console.Error.Flush(); + } + Environment.Exit(1); + return new Exception("APPEXIT"); + } + + static Exception PrintUsage() + { + Console.Error.WriteLine("Usage: --transport transport_spec app"); + Console.Error.WriteLine(); + Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ MyApp.exe"); + Console.Error.Flush(); + return Die(null); + } + + static CommandLineArgs ParseCommandLineArgs(string[] args) + { + var rv = new CommandLineArgs(); + Action next = null; + try + { + foreach (var arg in args) + { + if (next != null) + { + next(arg); + next = null; + } + else if (arg == "--transport") + next = a => rv.Transport = new Uri(a, UriKind.Absolute); + else if (rv.AppPath == null) + rv.AppPath = arg; + else + PrintUsage(); + + } + if (rv.AppPath == null || rv.Transport == null) + PrintUsage(); + } + catch + { + PrintUsage(); + } + return rv; + } + + static IAvaloniaRemoteTransportConnection CreateTransport(Uri transport) + { + if (transport.Scheme == "tcp-bson") + { + return new BsonTcpTransport().Connect(IPAddress.Parse(transport.Host), transport.Port).Result; + } + PrintUsage(); + return null; + } + + interface IAppInitializer + { + Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj); + } + + class AppInitializer : IAppInitializer where T : AppBuilderBase, new() + { + public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj) + { + var builder = (AppBuilderBase) obj; + builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport)); + builder.SetupWithoutStarting(); + return builder.Instance; + } + } + + private const string BuilderMethodName = "BuildAvaloniaApp"; + + class NeverClose : ICloseable + { + public event EventHandler Closed; + } + + public static void Main(string[] cmdline) + { + var args = ParseCommandLineArgs(cmdline); + var transport = CreateTransport(args.Transport); + var asm = Assembly.LoadFile(System.IO.Path.GetFullPath(args.AppPath)); + var entryPoint = asm.EntryPoint; + if (entryPoint == null) + throw Die($"Assembly {args.AppPath} doesn't have an entry point"); + var builderMethod = entryPoint.DeclaringType.GetMethod(BuilderMethodName, + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (builderMethod == null) + throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}"); + + var appBuilder = builderMethod.Invoke(null, null); + var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType())); + var app = initializer.GetConfiguredApp(transport, appBuilder); + s_transport = transport; + transport.OnMessage += OnTransportMessage; + transport.OnException += (t, e) => Die(e.ToString()); + app.Run(new NeverClose()); + } + + + private static void RebuildPreFlight() + { + PreviewerWindowingPlatform.PreFlightMessages = new List + { + s_supportedPixelFormats, + s_viewportAllocatedMessage + }; + } + + private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.InvokeAsync(() => + { + if (obj is ClientSupportedPixelFormatsMessage formats) + { + s_supportedPixelFormats = formats; + RebuildPreFlight(); + } + if (obj is ClientViewportAllocatedMessage viewport) + { + s_viewportAllocatedMessage = viewport; + RebuildPreFlight(); + } + if (obj is UpdateXamlMessage xaml) + { + try + { + DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath); + s_transport.Send(new UpdateXamlResultMessage()); + } + catch (Exception e) + { + s_transport.Send(new UpdateXamlResultMessage + { + Error = e.ToString() + }); + } + } + }); + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs similarity index 97% rename from src/tools/Avalonia.Designer.HostApp/Stubs.cs rename to src/Avalonia.DesignerSupport/Remote/Stubs.cs index a4160e7da4..5aad114cf7 100644 --- a/src/tools/Avalonia.Designer.HostApp/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -11,12 +11,8 @@ using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Rendering; -namespace Avalonia.Designer.HostApp +namespace Avalonia.DesignerSupport.Remote { - /// - /// Popups are no-op - /// - class WindowStub : IPopupImpl, IWindowImpl { public Action Deactivated { get; set; } diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index f0f3b12cc5..2a68862f18 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -4,7 +4,6 @@ netcoreapp2.0 - diff --git a/src/tools/Avalonia.Designer.HostApp/Program.cs b/src/tools/Avalonia.Designer.HostApp/Program.cs index 88dadf479e..29640ffc6f 100644 --- a/src/tools/Avalonia.Designer.HostApp/Program.cs +++ b/src/tools/Avalonia.Designer.HostApp/Program.cs @@ -1,173 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Reflection; -using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.DesignerSupport; -using Avalonia.Input; -using Avalonia.Remote.Protocol; -using Avalonia.Remote.Protocol.Designer; -using Avalonia.Remote.Protocol.Viewport; -using Avalonia.Threading; + namespace Avalonia.Designer.HostApp { class Program { - - private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats; - private static ClientViewportAllocatedMessage s_viewportAllocatedMessage; - private static IAvaloniaRemoteTransportConnection s_transport; - class CommandLineArgs - { - public string AppPath { get; set; } - public Uri Transport { get; set; } - } - - static Exception Die(string error) - { - if (error != null) - { - Console.Error.WriteLine(error); - Console.Error.Flush(); - } - Environment.Exit(1); - return new Exception("APPEXIT"); - } - - static Exception PrintUsage() - { - Console.Error.WriteLine("Usage: --transport transport_spec app"); - Console.Error.WriteLine(); - Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ MyApp.exe"); - Console.Error.Flush(); - return Die(null); - } - - static CommandLineArgs ParseCommandLineArgs(string[] args) - { - var rv = new CommandLineArgs(); - Action next = null; - try - { - foreach (var arg in args) - { - if (next != null) - { - next(arg); - next = null; - } - else if (arg == "--transport") - next = a => rv.Transport = new Uri(a, UriKind.Absolute); - else if (rv.AppPath == null) - rv.AppPath = arg; - else - PrintUsage(); - - } - if (rv.AppPath == null || rv.Transport == null) - PrintUsage(); - } - catch - { - PrintUsage(); - } - return rv; - } - - static IAvaloniaRemoteTransportConnection CreateTransport(Uri transport) - { - if (transport.Scheme == "tcp-bson") - { - return new BsonTcpTransport().Connect(IPAddress.Parse(transport.Host), transport.Port).Result; - } - PrintUsage(); - return null; - } - - interface IAppInitializer - { - Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj); - } - - class AppInitializer : IAppInitializer where T : AppBuilderBase, new() - { - public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj) - { - var builder = (AppBuilderBase) obj; - builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport)); - builder.SetupWithoutStarting(); - return builder.Instance; - } - } - - private const string BuilderMethodName = "BuildAvaloniaApp"; - - class NeverClose : ICloseable - { - public event EventHandler Closed; - } - - static void Main(string[] cmdline) - { - var args = ParseCommandLineArgs(cmdline); - var transport = CreateTransport(args.Transport); - var asm = Assembly.LoadFile(System.IO.Path.GetFullPath(args.AppPath)); - var entryPoint = asm.EntryPoint; - if (entryPoint == null) - throw Die($"Assembly {args.AppPath} doesn't have an entry point"); - var builderMethod = entryPoint.DeclaringType.GetMethod(BuilderMethodName, - BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - if (builderMethod == null) - throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}"); - - var appBuilder = builderMethod.Invoke(null, null); - var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType())); - var app = initializer.GetConfiguredApp(transport, appBuilder); - s_transport = transport; - transport.OnMessage += OnTransportMessage; - transport.OnException += (t, e) => Die(e.ToString()); - app.Run(new NeverClose()); - } - - - private static void RebuildPreFlight() - { - PreviewerWindowingPlatform.PreFlightMessages = new List - { - s_supportedPixelFormats, - s_viewportAllocatedMessage - }; - } - - private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.InvokeAsync(() => - { - if (obj is ClientSupportedPixelFormatsMessage formats) - { - s_supportedPixelFormats = formats; - RebuildPreFlight(); - } - if (obj is ClientViewportAllocatedMessage viewport) - { - s_viewportAllocatedMessage = viewport; - RebuildPreFlight(); - } - if (obj is UpdateXamlMessage xaml) - { - try - { - DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath); - s_transport.Send(new UpdateXamlResultMessage()); - } - catch (Exception e) - { - s_transport.Send(new UpdateXamlResultMessage - { - Error = e.ToString() - }); - } - } - }); + public static void Main(string[] args) + => Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(args); } } \ No newline at end of file From 4f13d504967aa9be3ccd2123189dece3591d32ad Mon Sep 17 00:00:00 2001 From: Jurjen Biewenga Date: Thu, 28 Sep 2017 15:53:05 +0200 Subject: [PATCH 023/482] Added initial changes to allow the user to change the selected item while the dropdown is closed but selected and allows the user to open the dropdown by pressing down --- src/Avalonia.Controls/DropDown.cs | 24 ++++++++++++++++++++++++ src/Avalonia.Controls/ItemsControl.cs | 11 +++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index 5349fb1ca7..63fd6726b4 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -114,6 +114,30 @@ namespace Avalonia.Controls IsDropDownOpen = false; e.Handled = true; } + + if (!IsDropDownOpen) + { + if (e.Key == Key.Right) + { + if (++SelectedIndex >= ItemCount) + SelectedIndex = 0; + + e.Handled = true; + } + else if (e.Key == Key.Left) + { + if (--SelectedIndex < 0) + SelectedIndex = ItemCount - 1; + e.Handled = true; + } + else if (e.Key == Key.Down) + { + IsDropDownOpen = true; + if (SelectedIndex == -1) + SelectedIndex = 0; + e.Handled = true; + } + } } } diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index aa209e0462..4366de1cd6 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -11,6 +11,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Controls.Utils; +using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Metadata; @@ -106,6 +107,12 @@ namespace Avalonia.Controls set { SetAndRaise(ItemsProperty, ref _items, value); } } + public int ItemCount + { + get; + private set; + } + /// /// Gets or sets the panel used to display the items. /// @@ -352,6 +359,10 @@ namespace Avalonia.Controls RemoveControlItemsFromLogicalChildren(e.OldItems); break; } + + int? count = (Items as IList)?.Count; + if (count != null) + ItemCount = (int)count; var collection = sender as ICollection; PseudoClasses.Set(":empty", collection == null || collection.Count == 0); From 12ec059acb3e83b718eabda9cf7f7ac6b8ed5043 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Oct 2017 18:08:37 -0500 Subject: [PATCH 024/482] Initial code to enable binding to a method. --- .../Data/ExpressionObserver.cs | 1 + .../Data/Plugins/MethodAccessorPlugin.cs | 91 +++++++++++++++++++ .../Data/ExpressionObserverTests_Method.cs | 65 +++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs create mode 100644 tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs b/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs index 1e55e17195..66d3beb907 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs +++ b/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs @@ -25,6 +25,7 @@ namespace Avalonia.Markup.Data new List { new AvaloniaPropertyAccessorPlugin(), + new MethodAccessorPlugin(), new InpcPropertyAccessorPlugin(), }; diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs new file mode 100644 index 0000000000..ef4281c314 --- /dev/null +++ b/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Data; +using System.Reflection; +using System.Linq; + +namespace Avalonia.Markup.Data.Plugins +{ + class MethodAccessorPlugin : IPropertyAccessorPlugin + { + public bool Match(object obj, string propertyName) + => obj.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == propertyName) != null; + + public IPropertyAccessor Start(WeakReference reference, string propertyName) + { + Contract.Requires(reference != null); + Contract.Requires(propertyName != null); + + var instance = reference.Target; + var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == propertyName); + + if (method != null) + { + return new Accessor(reference, method); + } + else + { + var message = $"Could not find CLR method '{propertyName}' on '{instance}'"; + var exception = new MissingMemberException(message); + return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); + } + } + + private class Accessor : PropertyAccessorBase + { + public Accessor(WeakReference reference, MethodInfo method) + { + Contract.Requires(reference != null); + Contract.Requires(method != null); + + var paramTypes = method.GetParameters().Select(param => param.ParameterType).ToArray(); + var returnType = method.ReturnType; + + // TODO: Throw exception if more than 8 parameters or more than 7 + return type. + // Do this here or in the caller? Here probably + if (returnType == typeof(void)) + { + if (paramTypes.Length == 0) + { + PropertyType = typeof(Action); + } + else + { + PropertyType = Type.GetType($"System.Action`{paramTypes.Length}").MakeGenericType(paramTypes); + } + } + else + { + var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray(); + PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters); + } + + // TODO: Is this going to leak? + // TODO: Static methods? + Value = method.CreateDelegate(PropertyType, reference.Target); + } + + public override Type PropertyType { get; } + + public override object Value { get; } + + public override bool SetValue(object value, BindingPriority priority) => false; + + protected override void SubscribeCore(IObserver observer) + { + SendCurrentValue(); + } + + private void SendCurrentValue() + { + try + { + var value = Value; + Observer.OnNext(value); + } + catch { } + } + } + } +} diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs new file mode 100644 index 0000000000..9929e4e986 --- /dev/null +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs @@ -0,0 +1,65 @@ +using Avalonia.Data; +using Avalonia.Markup.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Avalonia.Markup.UnitTests.Data +{ + public class ExpressionObserverTests_Method + { + private class TestObject + { + public void MethodWithoutReturn() { } + + public int MethodWithReturn() => 0; + + public int MethodWithReturnAndParameters(int i) => i; + } + + [Fact] + public async Task Should_Get_Method() + { + var data = new TestObject(); + var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithoutReturn)); + var result = await observer.Take(1); + + Assert.NotNull(result); + + GC.KeepAlive(data); + } + + [Theory] + [InlineData(nameof(TestObject.MethodWithoutReturn), typeof(Action))] + [InlineData(nameof(TestObject.MethodWithReturn), typeof(Func))] + [InlineData(nameof(TestObject.MethodWithReturnAndParameters), typeof(Func))] + public async Task Should_Get_Method_WithCorrectDelegateType(string methodName, Type expectedType) + { + var data = new TestObject(); + var observer = new ExpressionObserver(data, methodName); + var result = await observer.Take(1); + + Assert.IsType(expectedType, result); + + GC.KeepAlive(data); + } + + [Fact] + public async Task Can_Call_Method_Returned_From_Observer() + { + var data = new TestObject(); + var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithReturnAndParameters)); + var result = await observer.Take(1); + + var callback = (Func)result; + + Assert.Equal(1, callback(1)); + + GC.KeepAlive(data); + } + } +} From 250f6acef0e702cc420fa68460d6d04122d73e7e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Oct 2017 18:37:52 -0500 Subject: [PATCH 025/482] Add delegate -> command conversion as a default value conversion. --- .../AlwaysEnabledDelegateCommand.cs | 36 ++++++++++++++++++ .../Avalonia.Markup/DefaultValueConverter.cs | 6 +++ .../DefaultValueConverterTests.cs | 38 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs diff --git a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs b/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs new file mode 100644 index 0000000000..c675b876ae --- /dev/null +++ b/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Input; + +namespace Avalonia.Markup +{ + class AlwaysEnabledDelegateCommand : ICommand + { + private readonly Delegate action; + + public AlwaysEnabledDelegateCommand(Delegate action) + { + this.action = action; + } + + public event EventHandler CanExecuteChanged; + + public bool CanExecute(object parameter) + { + return true; + } + + public void Execute(object parameter) + { + if (action.Method.GetParameters().Length == 0) + { + action.DynamicInvoke(); + } + else + { + action.DynamicInvoke(parameter); + } + } + } +} diff --git a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs index b56291a653..bb6bd85827 100644 --- a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs +++ b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs @@ -5,6 +5,7 @@ using System; using System.Globalization; using Avalonia.Data; using Avalonia.Utilities; +using System.Windows.Input; namespace Avalonia.Markup { @@ -34,6 +35,11 @@ namespace Avalonia.Markup return AvaloniaProperty.UnsetValue; } + if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d) + { + return new AlwaysEnabledDelegateCommand(d); + } + if (TypeUtilities.TryConvert(targetType, value, culture, out object result)) { return result; diff --git a/tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs b/tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs index fd28f2e900..0aa2e00c0f 100644 --- a/tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs +++ b/tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs @@ -5,6 +5,8 @@ using System.Globalization; using Avalonia.Controls; using Avalonia.Data; using Xunit; +using System.Windows.Input; +using System; namespace Avalonia.Markup.UnitTests { @@ -118,6 +120,42 @@ namespace Avalonia.Markup.UnitTests Assert.IsType(result); } + [Fact] + public void Can_Convert_From_Delegate_To_Command() + { + int commandResult = 0; + + var result = DefaultValueConverter.Instance.Convert( + (Action)((int i) => { commandResult = i; }), + typeof(ICommand), + null, + CultureInfo.InvariantCulture); + + Assert.IsAssignableFrom(result); + + (result as ICommand).Execute(5); + + Assert.Equal(5, commandResult); + } + + [Fact] + public void Can_Convert_From_Delegate_To_Command_No_Parameters() + { + int commandResult = 0; + + var result = DefaultValueConverter.Instance.Convert( + (Action)(() => { commandResult = 1; }), + typeof(ICommand), + null, + CultureInfo.InvariantCulture); + + Assert.IsAssignableFrom(result); + + (result as ICommand).Execute(null); + + Assert.Equal(1, commandResult); + } + private enum TestEnum { Foo, From 81168ac09433147f8fe055bf7f8c618ea88d3fde Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Oct 2017 21:55:26 -0500 Subject: [PATCH 026/482] Add error handling for methods with too many parameters. Add support for static methods. --- .../AlwaysEnabledDelegateCommand.cs | 5 +-- .../Data/Plugins/MethodAccessorPlugin.cs | 41 ++++++++++--------- .../Data/ExpressionObserverTests_Method.cs | 22 ++++++++++ 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs b/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs index c675b876ae..1d417a55a6 100644 --- a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs +++ b/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs @@ -16,10 +16,7 @@ namespace Avalonia.Markup public event EventHandler CanExecuteChanged; - public bool CanExecute(object parameter) - { - return true; - } + public bool CanExecute(object parameter) => true; public void Execute(object parameter) { diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs index ef4281c314..c602fa4f4d 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs +++ b/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs @@ -9,24 +9,30 @@ namespace Avalonia.Markup.Data.Plugins { class MethodAccessorPlugin : IPropertyAccessorPlugin { - public bool Match(object obj, string propertyName) - => obj.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == propertyName) != null; + public bool Match(object obj, string methodName) + => obj.GetType().GetRuntimeMethods().Any(x => x.Name == methodName); - public IPropertyAccessor Start(WeakReference reference, string propertyName) + public IPropertyAccessor Start(WeakReference reference, string methodName) { Contract.Requires(reference != null); - Contract.Requires(propertyName != null); + Contract.Requires(methodName != null); var instance = reference.Target; - var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == propertyName); + var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == methodName); if (method != null) { + if (method.GetParameters().Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8) + { + var exception = new ArgumentException("Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", nameof(method)); + return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); + } + return new Accessor(reference, method); } else { - var message = $"Could not find CLR method '{propertyName}' on '{instance}'"; + var message = $"Could not find CLR method '{methodName}' on '{instance}'"; var exception = new MissingMemberException(message); return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); } @@ -41,9 +47,7 @@ namespace Avalonia.Markup.Data.Plugins var paramTypes = method.GetParameters().Select(param => param.ParameterType).ToArray(); var returnType = method.ReturnType; - - // TODO: Throw exception if more than 8 parameters or more than 7 + return type. - // Do this here or in the caller? Here probably + if (returnType == typeof(void)) { if (paramTypes.Length == 0) @@ -60,10 +64,8 @@ namespace Avalonia.Markup.Data.Plugins var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray(); PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters); } - - // TODO: Is this going to leak? - // TODO: Static methods? - Value = method.CreateDelegate(PropertyType, reference.Target); + + Value = method.IsStatic ? method.CreateDelegate(PropertyType) : method.CreateDelegate(PropertyType, reference.Target); } public override Type PropertyType { get; } @@ -73,19 +75,18 @@ namespace Avalonia.Markup.Data.Plugins public override bool SetValue(object value, BindingPriority priority) => false; protected override void SubscribeCore(IObserver observer) - { - SendCurrentValue(); - } - - private void SendCurrentValue() { try { - var value = Value; - Observer.OnNext(value); + Observer.OnNext(Value); } catch { } } + + private void SendCurrentValue() + { + + } } } } diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs index 9929e4e986..439e9dc542 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs @@ -19,6 +19,11 @@ namespace Avalonia.Markup.UnitTests.Data public int MethodWithReturn() => 0; public int MethodWithReturnAndParameters(int i) => i; + + public static void StaticMethod() { } + + public static void TooManyParameters(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { } + public static int TooManyParametersWithReturnType(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) => 1; } [Fact] @@ -37,6 +42,7 @@ namespace Avalonia.Markup.UnitTests.Data [InlineData(nameof(TestObject.MethodWithoutReturn), typeof(Action))] [InlineData(nameof(TestObject.MethodWithReturn), typeof(Func))] [InlineData(nameof(TestObject.MethodWithReturnAndParameters), typeof(Func))] + [InlineData(nameof(TestObject.StaticMethod), typeof(Action))] public async Task Should_Get_Method_WithCorrectDelegateType(string methodName, Type expectedType) { var data = new TestObject(); @@ -61,5 +67,21 @@ namespace Avalonia.Markup.UnitTests.Data GC.KeepAlive(data); } + + [Theory] + [InlineData(nameof(TestObject.TooManyParameters))] + [InlineData(nameof(TestObject.TooManyParametersWithReturnType))] + public async Task Should_Return_Error_Notification_If_Too_Many_Parameters(string methodName) + { + var data = new TestObject(); + var observer = new ExpressionObserver(data, methodName); + var result = await observer.Take(1); + + Assert.IsType(result); + + Assert.Equal(BindingErrorType.Error, ((BindingNotification)result).ErrorType); + + GC.KeepAlive(data); + } } } From 6dc6ec84e975bef1f65d75816a8b558e48b4bd46 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Oct 2017 21:57:47 -0500 Subject: [PATCH 027/482] Add leak test to make sure the method accessor doesn't leak memory. --- .../ExpressionObserverTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/Avalonia.LeakTests/ExpressionObserverTests.cs b/tests/Avalonia.LeakTests/ExpressionObserverTests.cs index 3dbc62424f..d0f892bebb 100644 --- a/tests/Avalonia.LeakTests/ExpressionObserverTests.cs +++ b/tests/Avalonia.LeakTests/ExpressionObserverTests.cs @@ -71,6 +71,28 @@ namespace Avalonia.LeakTests Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } + [Fact] + public void Should_Not_Keep_Source_Alive_MethodBinding() + { + Func run = () => + { + var source = new { Foo = new MethodBound() }; + var target = new ExpressionObserver(source, "Foo.A"); + target.Subscribe(_ => { }); + return target; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + + private class MethodBound + { + public void A() { } + } + private class NonIntegerIndexer : NotifyingBase { private readonly Dictionary _storage = new Dictionary(); From 05b6ab91c9873885e639e6ba4153b6828dde4edf Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Oct 2017 22:14:56 -0500 Subject: [PATCH 028/482] Disable unused event warning --- src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs b/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs index 1d417a55a6..6de9be80cc 100644 --- a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs +++ b/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs @@ -14,7 +14,9 @@ namespace Avalonia.Markup this.action = action; } +#pragma warning disable 0067 public event EventHandler CanExecuteChanged; +#pragma warning restore 0067 public bool CanExecute(object parameter) => true; From 685a509c5cf7b87f1157d2e8c5ac7f1c2497e0a9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 3 Oct 2017 17:00:49 +0200 Subject: [PATCH 029/482] Reimplement commented-out tests. --- tests/Avalonia.UnitTests/TestRoot.cs | 2 - .../Rendering/DeferredRendererTests.cs | 178 +++++++++--------- 2 files changed, 87 insertions(+), 93 deletions(-) diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 90532d64a1..9ec053f075 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -49,8 +49,6 @@ namespace Avalonia.UnitTests public ILayoutManager LayoutManager => AvaloniaLocator.Current.GetService(); - public IRenderTarget RenderTarget => null; - public IRenderer Renderer { get; set; } public IAccessKeyHandler AccessKeyHandler => null; diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 047cb05c26..cec95d4807 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -129,102 +129,98 @@ namespace Avalonia.Visuals.UnitTests.Rendering [Fact] public void Frame_Should_Create_Layer_For_Root() { - throw new NotImplementedException(); - //var loop = new Mock(); - //var root = new TestRoot(); - //var rootLayer = new Mock(); - //var dispatcher = new ImmediateDispatcher(); - - //var sceneBuilder = new Mock(); - //sceneBuilder.Setup(x => x.UpdateAll(It.IsAny())) - // .Callback(scene => - // { - // scene.Size = root.ClientSize; - // scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); - // }); - - //var layers = new Mock(); - //layers.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer()); - - //var renderInterface = new Mock(); - - //var target = new DeferredRenderer( - // root, - // loop.Object, - // sceneBuilder: sceneBuilder.Object, - // layerFactory: layers.Object, - // dispatcher: dispatcher); - - //target.Start(); - //RunFrame(loop); - - //layers.Verify(x => x.CreateLayer(root, root.ClientSize, 96, 96)); + var loop = new Mock(); + var root = new TestRoot(); + var rootLayer = new Mock(); + var dispatcher = new ImmediateDispatcher(); + + var sceneBuilder = new Mock(); + sceneBuilder.Setup(x => x.UpdateAll(It.IsAny())) + .Callback(scene => + { + scene.Size = root.ClientSize; + scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); + }); + + var renderInterface = new Mock(); + + var target = new DeferredRenderer( + root, + loop.Object, + sceneBuilder: sceneBuilder.Object, + //layerFactory: layers.Object, + dispatcher: dispatcher); + + target.Start(); + RunFrame(loop); + + var context = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null)); + context.Verify(x => x.CreateLayer(root.ClientSize)); } [Fact] public void Should_Create_And_Delete_Layers_For_Transparent_Controls() { - throw new NotImplementedException(); - //Border border; - //var root = new TestRoot - //{ - // Width = 100, - // Height = 100, - // Child = new Border - // { - // Background = Brushes.Red, - // Child = border = new Border - // { - // Background = Brushes.Green, - // } - // } - //}; - - //root.Measure(Size.Infinity); - //root.Arrange(new Rect(root.DesiredSize)); - - //var loop = new Mock(); - //var layerFactory = new MockRenderLayerFactory(new Dictionary - //{ - // { root, CreateLayer() }, - // { border, CreateLayer() }, - //}); - - //var target = new DeferredRenderer( - // root, - // loop.Object, - // layerFactory: layerFactory, - // dispatcher: new ImmediateDispatcher()); - //root.Renderer = target; - - //target.Start(); - //RunFrame(loop); - - //var rootContext = layerFactory.GetMockDrawingContext(root); - //var borderContext = layerFactory.GetMockDrawingContext(border); - - //rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); - //rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); - //borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); - - //rootContext.ResetCalls(); - //borderContext.ResetCalls(); - //border.Opacity = 0.5; - //RunFrame(loop); - - //rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); - //rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Never); - //borderContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); - - //rootContext.ResetCalls(); - //borderContext.ResetCalls(); - //border.Opacity = 1; - //RunFrame(loop); - - //layerFactory.GetMockBitmap(border).Verify(x => x.Dispose()); - //rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); - //rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); - //borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + Border border; + var root = new TestRoot + { + Width = 100, + Height = 100, + Child = new Border + { + Background = Brushes.Red, + Child = border = new Border + { + Background = Brushes.Green, + } + } + }; + + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var rootLayer = CreateLayer(); + var borderLayer = CreateLayer(); + var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null)); + renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny())) + .Returns(rootLayer) + .Returns(borderLayer); + + var loop = new Mock(); + var target = new DeferredRenderer( + root, + loop.Object, + dispatcher: new ImmediateDispatcher()); + root.Renderer = target; + + target.Start(); + RunFrame(loop); + + var rootContext = Mock.Get(rootLayer.CreateDrawingContext(null)); + var borderContext = Mock.Get(borderLayer.CreateDrawingContext(null)); + + rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + + rootContext.ResetCalls(); + borderContext.ResetCalls(); + border.Opacity = 0.5; + RunFrame(loop); + + rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Never); + borderContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + + rootContext.ResetCalls(); + borderContext.ResetCalls(); + border.Opacity = 1; + RunFrame(loop); + + Mock.Get(borderLayer).Verify(x => x.Dispose()); + rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); + rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); + borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } private void IgnoreFirstFrame(Mock loop, Mock sceneBuilder) From cf9e8cfede19978fbd55f731325d52c9a94fda91 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 3 Oct 2017 17:36:09 +0200 Subject: [PATCH 030/482] Use NotSupportedException --- .../Rendering/SceneGraph/DeferredDrawingContextImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 5fcd0e42bd..29c482c336 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -196,7 +196,7 @@ namespace Avalonia.Rendering.SceneGraph public IRenderTargetBitmapImpl CreateLayer(Size size) { - throw new NotImplementedException(); + throw new NotSupportedException("Creating layers on a deferred drawing context not supported"); } /// From 2cc4b41acc7521f72fe552cfbf079c85f06199dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Tue, 3 Oct 2017 22:12:11 +0100 Subject: [PATCH 031/482] Use Screen API. --- src/Avalonia.Controls/Screens.cs | 2 +- src/Avalonia.Controls/Window.cs | 11 ++++------- src/Windows/Avalonia.Win32/ScreenImpl.cs | 4 ++-- .../Avalonia.Controls.UnitTests/WindowTests.cs | 18 ++++++++++++------ 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs index b8ddce3aea..2bfddc048b 100644 --- a/src/Avalonia.Controls/Screens.cs +++ b/src/Avalonia.Controls/Screens.cs @@ -39,7 +39,7 @@ namespace Avalonia.Controls return currMaxScreen; } - public Screen SceenFromPoint(Point point) + public Screen ScreenFromPoint(Point point) { return All.FirstOrDefault(x=>x.Bounds.Contains(point)); } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 4cbd73841b..7890a7dd5a 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -376,13 +376,10 @@ namespace Avalonia.Controls { if (WindowStartupLocation == WindowStartupLocation.CenterScreen) { - // This should be using a Screen API, but we don't have one yet and - // PlatformImpl.MaxClientSize is the best we have. - if (PlatformImpl != null) - { - var positionAsSize = PlatformImpl.MaxClientSize / 2 - ClientSize / 2; - Position = new Point(positionAsSize.Width, positionAsSize.Height); - } + var screen = Screens.ScreenFromPoint(Bounds.Position); + + if (screen != null) + Position = screen.WorkingArea.CenterRect(new Rect(ClientSize)).Position; } else if (WindowStartupLocation == WindowStartupLocation.CenterOwner) { diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 4f4331e461..113b2811dc 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -41,8 +41,8 @@ namespace Avalonia.Win32 Rect avaloniaBounds = new Rect(bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top); Rect avaloniaWorkArea = - new Rect(workingArea.left, workingArea.top, workingArea.right - bounds.left, - workingArea.bottom - bounds.top); + new Rect(workingArea.left, workingArea.top, workingArea.right - workingArea.left, + workingArea.bottom - workingArea.top); screens[index] = new WinScreen(avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1, monitor); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 4de1cfe356..6168421919 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -239,32 +239,38 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Window_Should_Be_Centered_When_Window_Startup_Location_Is_Center_Screen() + public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { + var screen1 = new Mock(new Rect(new Size(1920, 1080)), new Rect(new Size(1920, 1040)), true); + var screen2 = new Mock(new Rect(new Size(1366, 768)), new Rect(new Size(1366, 728)), false); + + var screens = new Mock(); + screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object }); + var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Position); windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); - windowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1920, 1080)); windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.Screen).Returns(screens.Object); using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var window = new Window(); + var window = new Window(windowImpl.Object); window.WindowStartupLocation = WindowStartupLocation.CenterScreen; window.Position = new Point(60, 40); window.Show(); var expectedPosition = new Point( - window.PlatformImpl.MaxClientSize.Width / 2 - window.ClientSize.Width / 2, - window.PlatformImpl.MaxClientSize.Height / 2 - window.ClientSize.Height / 2); + screen1.Object.WorkingArea.Size.Width / 2 - window.ClientSize.Width / 2, + screen1.Object.WorkingArea.Size.Height / 2 - window.ClientSize.Height / 2); Assert.Equal(window.Position, expectedPosition); } } [Fact] - public void Window_Should_Be_Centered_Relative_To_Owner_When_Window_Startup_Location_Is_Center_Owner() + public void Window_Should_Be_Centered_Relative_To_Owner_When_WindowStartupLocation_Is_CenterOwner() { var parentWindowImpl = new Mock(); parentWindowImpl.SetupProperty(x => x.Position); From 097716141399b0a27a34887e109981a7ff1e9b6c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 11:23:48 +0300 Subject: [PATCH 032/482] Use AppDomain.GetLoadedAssemblies() for all platforms --- build/NetCore.props | 2 - .../NetCoreRuntimePlatform.cs | 42 ------------------- .../StandardRuntimePlatform.cs | 7 +--- 3 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs diff --git a/build/NetCore.props b/build/NetCore.props index cebb02c7f5..b9cde28015 100644 --- a/build/NetCore.props +++ b/build/NetCore.props @@ -1,6 +1,4 @@  - - diff --git a/src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs b/src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs deleted file mode 100644 index 6aab8e0243..0000000000 --- a/src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Microsoft.DotNet.PlatformAbstractions; -using Microsoft.Extensions.DependencyModel; - -namespace Avalonia.Shared.PlatformSupport -{ - internal partial class StandardRuntimePlatform - { - private static readonly Lazy Assemblies = new Lazy(LoadAssemblies); - public Assembly[] GetLoadedAssemblies() => Assemblies.Value; - - static Assembly[] LoadAssemblies() - { - var assemblies = new List(); - // Mostly copy-pasted from (MIT): - // https://github.com/StefH/System.AppDomain.Core/blob/0b35e676c2721aa367b96e62eb52c97ee0b43a70/src/System.AppDomain.NetCoreApp/AppDomain.cs - - foreach (var assemblyName in - DependencyContext.Default.GetRuntimeAssemblyNames(RuntimeEnvironment.GetRuntimeIdentifier())) - { - try - { - var assembly = Assembly.Load(assemblyName); - // just load all types and skip this assembly if one or more types cannot be resolved - assembly.DefinedTypes.ToArray(); - assemblies.Add(assembly); - } - catch (Exception ex) - { - Debug.Write(ex.Message); - } - } - return assemblies.ToArray(); - } - } -} diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs index 092910a08f..757cf52853 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs @@ -11,13 +11,8 @@ namespace Avalonia.Shared.PlatformSupport { internal partial class StandardRuntimePlatform : IRuntimePlatform { - -#if NETCOREAPP2_0 - public void PostThreadPoolItem(Action cb) => ThreadPool.QueueUserWorkItem(_ => cb(), null); -#else - public Assembly[] GetLoadedAssemblies() => AppDomain.CurrentDomain.GetAssemblies(); public void PostThreadPoolItem(Action cb) => ThreadPool.UnsafeQueueUserWorkItem(_ => cb(), null); -#endif + public Assembly[] GetLoadedAssemblies() => AppDomain.CurrentDomain.GetAssemblies(); public IDisposable StartSystemTimer(TimeSpan interval, Action tick) { return new Timer(_ => tick(), null, interval, interval); From b05f913e2501c47f6a42f8b9ddead69cb076723e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 12:15:22 +0300 Subject: [PATCH 033/482] [GTK3] Normal priority for timers --- src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs index be886ea1c7..5253be5afb 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs @@ -53,7 +53,7 @@ namespace Avalonia.Gtk3.Interop if (interval == 0) throw new ArgumentException("Don't know how to create a timer with zero or negative interval"); var timer = new Timer (); - GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Background), interval, + GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Normal), interval, () => { if (timer.Stopped) From f6a60e27592d89e76129a5f47157d62a2981b34d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 12:16:29 +0300 Subject: [PATCH 034/482] [GTK3] Block in SetNextRenderOperation until previous frame is dequeued --- src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index bfec3db756..8771106895 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using Avalonia.Controls; using Avalonia.Gtk3.Interop; using Avalonia.Input; @@ -29,6 +30,7 @@ namespace Avalonia.Gtk3 private GCHandle _gcHandle; private object _lock = new object(); private IDeferredRenderOperation _nextRenderOperation; + private readonly AutoResetEvent _canSetNextOperation = new AutoResetEvent(true); public WindowBaseImpl(GtkWindow gtkWidget) { @@ -255,11 +257,19 @@ namespace Avalonia.Gtk3 public void SetNextRenderOperation(IDeferredRenderOperation op) { - lock (_lock) + while (true) { - _nextRenderOperation?.Dispose(); - _nextRenderOperation = op; + lock (_lock) + { + if (_nextRenderOperation == null) + { + _nextRenderOperation = op; + return; + } + } + _canSetNextOperation.WaitOne(); } + } private void OnRenderTick() @@ -272,6 +282,7 @@ namespace Avalonia.Gtk3 op = _nextRenderOperation; _nextRenderOperation = null; } + _canSetNextOperation.Set(); } if (op != null) { From 5fd6f5ac6d315bc48ab15a9d25f65c0c7cbcc891 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 4 Oct 2017 13:22:59 +0200 Subject: [PATCH 035/482] Snap bounding boxes to pixels. --- .../SceneGraph/BrushDrawOperation.cs | 15 ++--- .../Rendering/SceneGraph/DrawOperation.cs | 26 +++++++++ .../Rendering/SceneGraph/GeometryNode.cs | 5 +- .../Rendering/SceneGraph/IDrawOperation.cs | 2 +- .../Rendering/SceneGraph/ImageNode.cs | 11 ++-- .../Rendering/SceneGraph/LineNode.cs | 5 +- .../Rendering/SceneGraph/OpacityMaskNode.cs | 5 +- .../Rendering/SceneGraph/RectangleNode.cs | 5 +- .../Rendering/SceneGraph/TextNode.cs | 5 +- .../SceneGraph/DrawOperationTests.cs | 55 +++++++++++++++++++ 10 files changed, 97 insertions(+), 37 deletions(-) create mode 100644 src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs index 59a895a22f..f56e7448a7 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using Avalonia.Media; -using Avalonia.Platform; using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph @@ -12,20 +11,16 @@ namespace Avalonia.Rendering.SceneGraph /// /// Base class for draw operations that can use a brush. /// - internal abstract class BrushDrawOperation : IDrawOperation + internal abstract class BrushDrawOperation : DrawOperation { - /// - public abstract Rect Bounds { get; } - - /// - public abstract bool HitTest(Point p); + public BrushDrawOperation(Rect bounds, Matrix transform, Pen pen) + : base(bounds, transform, pen) + { + } /// /// Gets a collection of child scenes that are needed to draw visual brushes. /// public abstract IDictionary ChildScenes { get; } - - /// - public abstract void Render(IDrawingContextImpl context); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs new file mode 100644 index 0000000000..4c6ed189ff --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs @@ -0,0 +1,26 @@ +using System; +using Avalonia.Media; +using Avalonia.Platform; + +namespace Avalonia.Rendering.SceneGraph +{ + /// + /// Base class for draw operations that have bounds. + /// + internal abstract class DrawOperation : IDrawOperation + { + public DrawOperation(Rect bounds, Matrix transform, Pen pen) + { + bounds = bounds.Inflate((pen?.Thickness ?? 0) / 2).TransformToAABB(transform); + Bounds = new Rect( + new Point(Math.Floor(bounds.X), Math.Floor(bounds.Y)), + new Point(Math.Ceiling(bounds.Right), Math.Ceiling(bounds.Bottom))); + } + + public Rect Bounds { get; } + + public abstract bool HitTest(Point p); + + public abstract void Render(IDrawingContextImpl context); + } +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs index b884c42d99..6310122183 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs @@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph Pen pen, IGeometryImpl geometry, IDictionary childScenes = null) + : base(geometry.GetRenderBounds(pen?.Thickness ?? 0), transform, null) { - Bounds = geometry.GetRenderBounds(pen?.Thickness ?? 0).TransformToAABB(transform); Transform = transform; Brush = brush?.ToImmutable(); Pen = pen?.ToImmutable(); @@ -37,9 +37,6 @@ namespace Avalonia.Rendering.SceneGraph ChildScenes = childScenes; } - /// - public override Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs index 8c3bb72463..839fd9b0e5 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs @@ -12,7 +12,7 @@ namespace Avalonia.Rendering.SceneGraph public interface IDrawOperation { /// - /// Gets the bounds of the visible content in the node. + /// Gets the bounds of the visible content in the node in global coordinates. /// Rect Bounds { get; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index 4a50f12095..8291d1c0bb 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -9,7 +9,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// A node in the scene graph which represents an image draw. /// - internal class ImageNode : IDrawOperation + internal class ImageNode : DrawOperation { /// /// Initializes a new instance of the class. @@ -20,8 +20,8 @@ namespace Avalonia.Rendering.SceneGraph /// The source rect. /// The destination rect. public ImageNode(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect) + : base(destRect, transform, null) { - Bounds = destRect.TransformToAABB(transform); Transform = transform; Source = source; Opacity = opacity; @@ -29,9 +29,6 @@ namespace Avalonia.Rendering.SceneGraph DestRect = destRect; } - /// - public Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// @@ -80,7 +77,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public void Render(IDrawingContextImpl context) + public override void Render(IDrawingContextImpl context) { // TODO: Probably need to introduce some kind of locking mechanism in the case of // WriteableBitmap. @@ -89,6 +86,6 @@ namespace Avalonia.Rendering.SceneGraph } /// - public bool HitTest(Point p) => Bounds.Contains(p); + public override bool HitTest(Point p) => Bounds.Contains(p); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs index e39335b5b6..d3df478a63 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs @@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph Point p1, Point p2, IDictionary childScenes = null) + : base(new Rect(p1, p2), transform, pen) { - Bounds = new Rect(p1, p2).TransformToAABB(transform).Inflate(pen?.Thickness ?? 0); Transform = transform; Pen = pen?.ToImmutable(); P1 = p1; @@ -37,9 +37,6 @@ namespace Avalonia.Rendering.SceneGraph ChildScenes = childScenes; } - /// - public override Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs index c40869724f..28b8f53e26 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs @@ -19,6 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The bounds of the mask. /// Child scenes for drawing visual brushes. public OpacityMaskNode(IBrush mask, Rect bounds, IDictionary childScenes = null) + : base(Rect.Empty, Matrix.Identity, null) { Mask = mask?.ToImmutable(); MaskBounds = bounds; @@ -30,12 +31,10 @@ namespace Avalonia.Rendering.SceneGraph /// opacity mask pop. /// public OpacityMaskNode() + : base(Rect.Empty, Matrix.Identity, null) { } - /// - public override Rect Bounds => Rect.Empty; - /// /// Gets the mask to be pushed or null if the operation represents a pop. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs index 2affc454b5..1730621c55 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs @@ -30,8 +30,8 @@ namespace Avalonia.Rendering.SceneGraph Rect rect, float cornerRadius, IDictionary childScenes = null) + : base(rect, transform, pen) { - Bounds = rect.TransformToAABB(transform).Inflate(pen?.Thickness ?? 0); Transform = transform; Brush = brush?.ToImmutable(); Pen = pen?.ToImmutable(); @@ -40,9 +40,6 @@ namespace Avalonia.Rendering.SceneGraph ChildScenes = childScenes; } - /// - public override Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs index 058f3b1c22..6328d7dd14 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs @@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph Point origin, IFormattedTextImpl text, IDictionary childScenes = null) + : base(new Rect(origin, text.Size), transform, null) { - Bounds = new Rect(origin, text.Size).TransformToAABB(transform); Transform = transform; Foreground = foreground?.ToImmutable(); Origin = origin; @@ -37,9 +37,6 @@ namespace Avalonia.Rendering.SceneGraph ChildScenes = childScenes; } - /// - public override Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs new file mode 100644 index 0000000000..76fe103c1b --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs @@ -0,0 +1,55 @@ +using System; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering.SceneGraph; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public class DrawOperationTests + { + [Fact] + public void Empty_Bounds_Remain_Empty() + { + var target = new TestDrawOperation(Rect.Empty, Matrix.Identity, null); + + Assert.Equal(Rect.Empty, target.Bounds); + } + + [Theory] + [InlineData(10, 10, 10, 10, 1, 1, 1, 9, 9, 12, 12)] + [InlineData(10, 10, 10, 10, 1, 1, 2, 9, 9, 12, 12)] + [InlineData(10, 10, 10, 10, 1.5, 1.5, 1, 14, 14, 17, 17)] + public void Rectangle_Bounds_Are_Snapped_To_Pixels( + double x, + double y, + double width, + double height, + double scaleX, + double scaleY, + double? penThickness, + double expectedX, + double expectedY, + double expectedWidth, + double expectedHeight) + { + var target = new TestDrawOperation( + new Rect(x, y, width, height), + Matrix.CreateScale(scaleX, scaleY), + penThickness.HasValue ? new Pen(Brushes.Black, penThickness.Value) : null); + Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), target.Bounds); + } + + private class TestDrawOperation : DrawOperation + { + public TestDrawOperation(Rect bounds, Matrix transform, Pen pen) + :base(bounds, transform, pen) + { + } + + public override bool HitTest(Point p) => false; + + public override void Render(IDrawingContextImpl context) { } + } + } +} From eb5a354d624f49f4a82f0574a9d4b604db0acf4f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 4 Oct 2017 13:13:07 +0100 Subject: [PATCH 036/482] [StackPanel] Gap value is not added to the measured width or height after the last child. --- src/Avalonia.Controls/StackPanel.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index 0e12fb3283..a6fe35d668 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -170,6 +170,15 @@ namespace Avalonia.Controls } } + if (Orientation == Orientation.Vertical) + { + measuredHeight -= gap; + } + else + { + measuredWidth -= gap; + } + return new Size(measuredWidth, measuredHeight); } From 29791fa241976caa9de681eddc6791669c48aa39 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 15:27:18 +0300 Subject: [PATCH 037/482] Manual native memory management for leak detection --- .../Platform/IRuntimePlatform.cs | 9 ++ .../Avalonia.DotNetCoreRuntime.csproj | 3 +- .../Avalonia.Gtk3/ImageSurfaceFramebuffer.cs | 20 +-- .../Interop/ManagedCairoSurface.cs | 38 +++++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 4 + .../StandardRuntimePlatform.cs | 132 +++++++++++++++++- src/Skia/Avalonia.Skia/BitmapImpl.cs | 13 ++ 7 files changed, 207 insertions(+), 12 deletions(-) create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs diff --git a/src/Avalonia.Base/Platform/IRuntimePlatform.cs b/src/Avalonia.Base/Platform/IRuntimePlatform.cs index e1a09f094d..66253dc5b2 100644 --- a/src/Avalonia.Base/Platform/IRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/IRuntimePlatform.cs @@ -14,6 +14,15 @@ namespace Avalonia.Platform IDisposable StartSystemTimer(TimeSpan interval, Action tick); string GetStackTrace(); RuntimePlatformInfo GetRuntimeInfo(); + IUnmanagedBlob AllocBlob(int size); + } + + public interface IUnmanagedBlob : IDisposable + { + IntPtr Address { get; } + int Size { get; } + bool IsDisposed { get; } + } public struct RuntimePlatformInfo diff --git a/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj b/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj index eade213c4c..53b2c997d0 100644 --- a/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj +++ b/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj @@ -2,6 +2,7 @@ netcoreapp2.0 false + $(DefineConstants);DOTNETCORE bin\$(Configuration)\Avalonia.DotNetCoreRuntime.XML @@ -21,5 +22,5 @@ - + \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs index 182da7df58..4ebb080dd0 100644 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -16,7 +16,7 @@ namespace Avalonia.Gtk3 { private readonly WindowBaseImpl _impl; private readonly GtkWidget _widget; - private CairoSurface _surface; + private ManagedCairoSurface _surface; private int _factor; private object _lock = new object(); public ImageSurfaceFramebuffer(WindowBaseImpl impl, int width, int height, int factor) @@ -26,13 +26,13 @@ namespace Avalonia.Gtk3 _factor = factor; width *= _factor; height *= _factor; - _surface = Native.CairoImageSurfaceCreate(1, width, height); + _surface = new ManagedCairoSurface(width, height); Width = width; Height = height; - Address = Native.CairoImageSurfaceGetData(_surface); - RowBytes = Native.CairoImageSurfaceGetStride(_surface); - Native.CairoSurfaceFlush(_surface); + Address = _surface.Buffer; + RowBytes = _surface.Stride; + Native.CairoSurfaceFlush(_surface.Surface); } static void Draw(IntPtr context, CairoSurface surface, double factor) @@ -83,12 +83,12 @@ namespace Avalonia.Gtk3 class RenderOp : IDeferredRenderOperation { private readonly GtkWidget _widget; - private CairoSurface _surface; + private ManagedCairoSurface _surface; private readonly double _factor; private readonly int _width; private readonly int _height; - public RenderOp(GtkWidget widget, CairoSurface _surface, double factor, int width, int height) + public RenderOp(GtkWidget widget, ManagedCairoSurface _surface, double factor, int width, int height) { _widget = widget; this._surface = _surface; @@ -105,7 +105,7 @@ namespace Avalonia.Gtk3 public void RenderNow() { - DrawToWidget(_widget, _surface, _width, _height, _factor); + DrawToWidget(_widget, _surface.Surface, _width, _height, _factor); } } @@ -116,9 +116,9 @@ namespace Avalonia.Gtk3 if (Dispatcher.UIThread.CheckAccess()) { if (_impl.CurrentCairoContext != IntPtr.Zero) - Draw(_impl.CurrentCairoContext, _surface, _factor); + Draw(_impl.CurrentCairoContext, _surface.Surface, _factor); else - DrawToWidget(_widget, _surface, Width, Height, _factor); + DrawToWidget(_widget, _surface.Surface, Width, Height, _factor); _surface.Dispose(); } else diff --git a/src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs b/src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs new file mode 100644 index 0000000000..2cde99afa5 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Platform; + +namespace Avalonia.Gtk3.Interop +{ + class ManagedCairoSurface : IDisposable + { + public IntPtr Buffer { get; private set; } + public CairoSurface Surface { get; private set; } + public int Stride { get; private set; } + private int _size; + private IRuntimePlatform _plat; + private IUnmanagedBlob _blob; + + public ManagedCairoSurface(int width, int height) + { + _plat = AvaloniaLocator.Current.GetService(); + Stride = width * 4; + _size = height * Stride; + _blob = _plat.AllocBlob(_size * 2); + Buffer = _blob.Address; + Surface = Native.CairoImageSurfaceCreateForData(Buffer, 1, width, height, Stride); + } + + public void Dispose() + { + + if (Buffer != IntPtr.Zero) + { + Surface.Dispose(); + _blob.Dispose(); + Buffer = IntPtr.Zero; + } + } + + } +} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index c96fb23366..433094e0ae 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -160,6 +160,9 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] public delegate CairoSurface cairo_image_surface_create(int format, int width, int height); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate CairoSurface cairo_image_surface_create_for_data(IntPtr data, int format, int width, int height, int stride); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] public delegate IntPtr cairo_image_surface_get_data(CairoSurface surface); @@ -459,6 +462,7 @@ namespace Avalonia.Gtk3.Interop public static D.gdk_cairo_create GdkCairoCreate; public static D.cairo_image_surface_create CairoImageSurfaceCreate; + public static D.cairo_image_surface_create_for_data CairoImageSurfaceCreateForData; 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; diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs index 757cf52853..d648b630bd 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs @@ -2,8 +2,11 @@ // 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.Reflection; using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using Avalonia.Platform; @@ -18,8 +21,135 @@ namespace Avalonia.Shared.PlatformSupport return new Timer(_ => tick(), null, interval, interval); } + public string GetStackTrace() => Environment.StackTrace; + + public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(this, size); + + class UnmanagedBlob : IUnmanagedBlob + { + private readonly StandardRuntimePlatform _plat; +#if DEBUG + private static readonly List Backtraces = new List(); + private static Thread GCThread; + private readonly string _backtrace; - public string GetStackTrace() => Environment.StackTrace; + class GCThreadDetector + { + ~GCThreadDetector() + { + GCThread = Thread.CurrentThread; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Spawn() => new GCThreadDetector(); + + static UnmanagedBlob() + { + Spawn(); + GC.WaitForPendingFinalizers(); + } + +#endif + + public UnmanagedBlob(StandardRuntimePlatform plat, int size) + { + _plat = plat; + Address = plat.Alloc(size); + GC.AddMemoryPressure(size); + Size = size; +#if DEBUG + _backtrace = Environment.StackTrace; + Backtraces.Add(_backtrace); +#endif + } + + void DoDispose() + { + if (!IsDisposed) + { + Backtraces.Remove(_backtrace); + _plat.Free(Address, Size); + GC.RemoveMemoryPressure(Size); + IsDisposed = true; + Address = IntPtr.Zero; + } + } + + public void Dispose() + { +#if DEBUG + if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId) + { + Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: " + + Environment.StackTrace + + "\n\nBlob created by " + _backtrace); + } +#endif + DoDispose(); + GC.SuppressFinalize(this); + } + + ~UnmanagedBlob() + { +#if DEBUG + Console.Error.WriteLine("Undisposed native blob created by " + _backtrace); +#endif + DoDispose(); + } + + public IntPtr Address { get; private set; } + public int Size { get; private set; } + public bool IsDisposed { get; private set; } + } + + + +#if FULLDOTNET || DOTNETCORE + [DllImport("libc", SetLastError = true)] + private static extern IntPtr mmap(IntPtr addr, IntPtr length, int prot, int flags, int fd, IntPtr offset); + [DllImport("libc", SetLastError = true)] + private static extern int munmap(IntPtr addr, IntPtr length); + [DllImport("libc", SetLastError = true)] + private static extern long sysconf(int name); + + private bool? _useMmap; + private bool UseMmap + => _useMmap ?? ((_useMmap = GetRuntimeInfo().OperatingSystem == OperatingSystemType.Linux)).Value; + + IntPtr Alloc(int size) + { + if (UseMmap) + { + var rv = mmap(IntPtr.Zero, new IntPtr(size), 3, 0x22, -1, IntPtr.Zero); + if (rv.ToInt64() == -1 || (ulong) rv.ToInt64() == 0xffffffff) + { + var errno = Marshal.GetLastWin32Error(); + throw new Exception("Unable to allocate memory: " + errno); + } + return rv; + } + else + return Marshal.AllocHGlobal(size); + } + + void Free(IntPtr ptr, int len) + { + if (UseMmap) + { + if (munmap(ptr, new IntPtr(len)) == -1) + { + var errno = Marshal.GetLastWin32Error(); + throw new Exception("Unable to free memory: " + errno); + } + } + else + Marshal.FreeHGlobal(ptr); + } +#else + IntPtr Alloc(int size) => Marshal.AllocHGlobal(size); + void Free(IntPtr ptr, int len) => Marshal.FreeHGlobal(ptr); +#endif } } \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia/BitmapImpl.cs b/src/Skia/Avalonia.Skia/BitmapImpl.cs index ce3efded11..d13190deff 100644 --- a/src/Skia/Avalonia.Skia/BitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/BitmapImpl.cs @@ -20,6 +20,13 @@ namespace Avalonia.Skia _dpi = new Vector(96, 96); } + static void ReleaseProc(IntPtr address, object ctx) + { + ((IUnmanagedBlob) ctx).Dispose(); + } + + private static readonly SKBitmapReleaseDelegate ReleaseDelegate = ReleaseProc; + public BitmapImpl(int width, int height, Vector dpi, PixelFormat? fmt = null) { PixelHeight = height; @@ -29,6 +36,12 @@ namespace Avalonia.Skia var runtime = AvaloniaLocator.Current?.GetService()?.GetRuntimeInfo(); if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux) colorType = SKColorType.Bgra8888; + + Bitmap = new SKBitmap(); + var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul); + var plat = AvaloniaLocator.Current.GetService(); + var blob = plat.AllocBlob(nfo.BytesSize); + Bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, ReleaseDelegate, blob); Bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul); Bitmap.Erase(SKColor.Empty); } From 18e556a1b11c46a5f2061b736913f30174812b16 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 4 Oct 2017 13:37:01 +0100 Subject: [PATCH 038/482] correct stack panel unit tests. --- tests/Avalonia.Controls.UnitTests/StackPanelTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs index f75c126ff1..56412d732b 100644 --- a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs @@ -70,7 +70,7 @@ namespace Avalonia.Controls.UnitTests target.Measure(Size.Infinity); target.Arrange(new Rect(target.DesiredSize)); - Assert.Equal(new Size(120, 130), target.Bounds.Size); + Assert.Equal(new Size(120, 120), target.Bounds.Size); Assert.Equal(new Rect(0, 0, 120, 20), target.Children[0].Bounds); Assert.Equal(new Rect(0, 30, 120, 30), target.Children[1].Bounds); Assert.Equal(new Rect(0, 70, 120, 50), target.Children[2].Bounds); @@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests target.Measure(Size.Infinity); target.Arrange(new Rect(target.DesiredSize)); - Assert.Equal(new Size(130, 120), target.Bounds.Size); + Assert.Equal(new Size(120, 120), target.Bounds.Size); Assert.Equal(new Rect(0, 0, 20, 120), target.Children[0].Bounds); Assert.Equal(new Rect(30, 0, 30, 120), target.Children[1].Bounds); Assert.Equal(new Rect(70, 0, 50, 120), target.Children[2].Bounds); From 835bf9a7e5054a2078d01655c1546215d37cdb34 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 15:45:37 +0300 Subject: [PATCH 039/482] Fixed release build --- src/Shared/PlatformSupport/StandardRuntimePlatform.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs index d648b630bd..fafa17a810 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs @@ -69,7 +69,9 @@ namespace Avalonia.Shared.PlatformSupport { if (!IsDisposed) { +#if DEBUG Backtraces.Remove(_backtrace); +#endif _plat.Free(Address, Size); GC.RemoveMemoryPressure(Size); IsDisposed = true; From 38053170425ea333c70b79128eeae7ec064da696 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 17:31:22 +0300 Subject: [PATCH 040/482] [SKIA] Fall back to direct SKBitmap call for unit tests --- src/Skia/Avalonia.Skia/BitmapImpl.cs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Skia/Avalonia.Skia/BitmapImpl.cs b/src/Skia/Avalonia.Skia/BitmapImpl.cs index d13190deff..49d7d355ec 100644 --- a/src/Skia/Avalonia.Skia/BitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/BitmapImpl.cs @@ -33,16 +33,22 @@ namespace Avalonia.Skia PixelWidth = width; _dpi = dpi; var colorType = fmt?.ToSkColorType() ?? SKImageInfo.PlatformColorType; - var runtime = AvaloniaLocator.Current?.GetService()?.GetRuntimeInfo(); + var runtimePlatform = AvaloniaLocator.Current?.GetService() + var runtime = runtimePlatform?.GetRuntimeInfo(); if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux) colorType = SKColorType.Bgra8888; - - Bitmap = new SKBitmap(); - var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul); - var plat = AvaloniaLocator.Current.GetService(); - var blob = plat.AllocBlob(nfo.BytesSize); - Bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, ReleaseDelegate, blob); - Bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul); + + if (runtimePlatform != null) + { + Bitmap = new SKBitmap(); + var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul); + var plat = AvaloniaLocator.Current.GetService(); + var blob = plat.AllocBlob(nfo.BytesSize); + Bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, ReleaseDelegate, blob); + + } + else + Bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul); Bitmap.Erase(SKColor.Empty); } From 004f8a4cdde88168c6b4ebcb3a6a8b9e123d19f4 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 17:32:53 +0300 Subject: [PATCH 041/482] Fall back to direct SKBitmap --- src/Skia/Avalonia.Skia/BitmapImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/BitmapImpl.cs b/src/Skia/Avalonia.Skia/BitmapImpl.cs index 49d7d355ec..00ab770e01 100644 --- a/src/Skia/Avalonia.Skia/BitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/BitmapImpl.cs @@ -33,7 +33,7 @@ namespace Avalonia.Skia PixelWidth = width; _dpi = dpi; var colorType = fmt?.ToSkColorType() ?? SKImageInfo.PlatformColorType; - var runtimePlatform = AvaloniaLocator.Current?.GetService() + var runtimePlatform = AvaloniaLocator.Current?.GetService(); var runtime = runtimePlatform?.GetRuntimeInfo(); if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux) colorType = SKColorType.Bgra8888; From 372ae88ba926268fe6cdb1e363506ed1baee9364 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 17:50:31 +0300 Subject: [PATCH 042/482] Set NativeBlob.Size to zero on dispose --- src/Shared/PlatformSupport/StandardRuntimePlatform.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs index fafa17a810..b777736f06 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs @@ -76,6 +76,7 @@ namespace Avalonia.Shared.PlatformSupport GC.RemoveMemoryPressure(Size); IsDisposed = true; Address = IntPtr.Zero; + Size = 0; } } From 1993942e9e0ed329f6c3c40174f9f7a6e01122de Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 18:25:10 +0300 Subject: [PATCH 043/482] [SKIA] Properly dispose intermediate bitmaps --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index eadcc05744..e549f06bd5 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -112,6 +112,7 @@ namespace Avalonia.Skia public readonly SKPaint Paint; private IDisposable _disposable1; + private IDisposable _disposable2; public IDisposable ApplyTo(SKPaint paint) { @@ -127,6 +128,8 @@ namespace Avalonia.Skia { if (_disposable1 == null) _disposable1 = disposable; + else if (_disposable2 == null) + _disposable2 = disposable; else throw new InvalidOperationException(); } @@ -135,12 +138,14 @@ namespace Avalonia.Skia { Paint = paint; _disposable1 = null; + _disposable2 = null; } public void Dispose() { Paint?.Dispose(); _disposable1?.Dispose(); + _disposable2?.Dispose(); } } @@ -221,8 +226,8 @@ namespace Avalonia.Skia _visualBrushRenderer.RenderVisualBrush(ctx, visualBrush); } - rv.AddDisposable(tileBrushImage); tileBrushImage = intermediate; + rv.AddDisposable(tileBrushImage); } } else From 10551b7b312a81703779d92d1026ff4f75173eb2 Mon Sep 17 00:00:00 2001 From: Jurjen Biewenga Date: Wed, 4 Oct 2017 17:30:15 +0200 Subject: [PATCH 044/482] Added array to filter out specific chars from the input text Added '' to the invalid chars array --- src/Avalonia.Input/KeyboardDevice.cs | 34 ++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index d815f8082b..76656ee79e 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -23,6 +23,8 @@ namespace Avalonia.Input public IInputManager InputManager => AvaloniaLocator.Current.GetService(); public IFocusManager FocusManager => AvaloniaLocator.Current.GetService(); + + public string[] invalidCharacters = new String[1]{""}; public IInputElement FocusedElement { @@ -122,18 +124,32 @@ namespace Avalonia.Input if (text != null) { - var ev = new TextInputEventArgs() + string cleanedText = RemoveInvalidCharacters(text.Text); + if (!string.IsNullOrEmpty(cleanedText)) { - Device = this, - Text = text.Text, - Source = element, - RoutedEvent = InputElement.TextInputEvent - }; - - element.RaiseEvent(ev); - e.Handled = ev.Handled; + var ev = new TextInputEventArgs() + { + Device = this, + Text = cleanedText, + Source = element, + RoutedEvent = InputElement.TextInputEvent + }; + + element.RaiseEvent(ev); + e.Handled = ev.Handled; + } } } } + + public string RemoveInvalidCharacters(string text) + { + for (var i = 0; i < invalidCharacters.Length; i++) + { + text = text.Replace(invalidCharacters[i], string.Empty); + } + + return text; + } } } From 3882cf6cf35b80e320f54d82dbe4d4d7d7f8061d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 18:30:54 +0300 Subject: [PATCH 045/482] [GTK3] Allow to render IDeferredRenderOperation to existing context --- src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs | 2 +- src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs | 11 +++++++---- src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs b/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs index 8c1456726c..e16463a2ef 100644 --- a/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs +++ b/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs @@ -4,6 +4,6 @@ namespace Avalonia.Gtk3 { public interface IDeferredRenderOperation : IDisposable { - void RenderNow(); + void RenderNow(IntPtr? ctx); } } \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs index 4ebb080dd0..efda21c753 100644 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -88,10 +88,10 @@ namespace Avalonia.Gtk3 private readonly int _width; private readonly int _height; - public RenderOp(GtkWidget widget, ManagedCairoSurface _surface, double factor, int width, int height) + public RenderOp(GtkWidget widget, ManagedCairoSurface surface, double factor, int width, int height) { _widget = widget; - this._surface = _surface; + _surface = surface ?? throw new ArgumentNullException(); _factor = factor; _width = width; _height = height; @@ -103,9 +103,12 @@ namespace Avalonia.Gtk3 _surface = null; } - public void RenderNow() + public void RenderNow(IntPtr? ctx) { - DrawToWidget(_widget, _surface.Surface, _width, _height, _factor); + if(ctx.HasValue) + Draw(ctx.Value, _surface.Surface, _factor); + else + DrawToWidget(_widget, _surface.Surface, _width, _height, _factor); } } diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 8771106895..d12ef23623 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -286,7 +286,7 @@ namespace Avalonia.Gtk3 } if (op != null) { - op?.RenderNow(); + op?.RenderNow(null); op?.Dispose(); } } From e3b3e05a0cf6455717a67921f8ca786f92a67076 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 4 Oct 2017 22:23:59 +0300 Subject: [PATCH 046/482] [GTK3] Use XPutImage on Linux when possible --- src/Gtk/Avalonia.Gtk3/FramebufferManager.cs | 32 ++++++++++++ src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 5 ++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 4 ++ src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 3 +- src/Gtk/Avalonia.Gtk3/X11.cs | 54 ++++++++++++++++++++ src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs | 55 +++++++++++++++++++++ 6 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/Gtk/Avalonia.Gtk3/X11.cs create mode 100644 src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs diff --git a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs index b0cc668569..455b63b89e 100644 --- a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs +++ b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Platform; +using Avalonia.Threading; namespace Avalonia.Gtk3 { @@ -27,7 +28,38 @@ namespace Avalonia.Gtk3 var s = _window.ClientSize; var width = (int) s.Width; var height = (int) s.Height; + + if (!Dispatcher.UIThread.CheckAccess() && Gtk3Platform.DisplayClassName.ToLower().Contains("x11")) + { + var x11 = LockX11Framebuffer(width, height); + if (x11 != null) + return x11; + } + + return new ImageSurfaceFramebuffer(_window, width, height, _window.LastKnownScaleFactor); } + + private static int X11ErrorHandler(IntPtr d, IntPtr e) + { + return 0; + } + + private static X11.XErrorHandler X11ErrorHandlerDelegate = X11ErrorHandler; + + private static IntPtr X11Display; + private ILockedFramebuffer LockX11Framebuffer(int width, int height) + { + if (!_window.GdkWindowHandle.HasValue) + return null; + if (X11Display == IntPtr.Zero) + { + X11Display = X11.XOpenDisplay(IntPtr.Zero); + if (X11Display == IntPtr.Zero) + return null; + X11.XSetErrorHandler(X11ErrorHandlerDelegate); + } + return new X11Framebuffer(X11Display, _window.GdkWindowHandle.Value, width, height, _window.LastKnownScaleFactor); + } } } diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index b36a1cda91..10405037ab 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -22,11 +23,15 @@ namespace Avalonia.Gtk3 internal static readonly MouseDevice Mouse = new MouseDevice(); internal static readonly KeyboardDevice Keyboard = new KeyboardDevice(); internal static IntPtr App { get; set; } + internal static string DisplayClassName; public static bool UseDeferredRendering = true; public static void Initialize() { Resolver.Resolve(); Native.GtkInit(0, IntPtr.Zero); + var disp = Native.GdkGetDefaultDisplay(); + DisplayClassName = Utf8Buffer.StringFromPtr(Native.GTypeName(Marshal.ReadIntPtr(Marshal.ReadIntPtr(disp)))); + using (var utf = new Utf8Buffer("avalonia.app." + Guid.NewGuid())) App = Native.GtkApplicationNew(utf, 0); //Mark current thread as UI thread diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 433094e0ae..15b3a11fbb 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -318,6 +318,9 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate void g_object_ref(GObject instance); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] + public delegate IntPtr g_type_name(IntPtr instance); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate ulong g_signal_connect_object(GObject instance, Utf8Buffer signal, IntPtr handler, IntPtr userData, int flags); @@ -410,6 +413,7 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_dialog_add_button GtkDialogAddButton; public static D.g_object_unref GObjectUnref; public static D.g_object_ref GObjectRef; + public static D.g_type_name GTypeName; public static D.g_signal_connect_object GSignalConnectObject; public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; public static D.g_timeout_add GTimeoutAdd; diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index d12ef23623..e14ed77877 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -31,7 +31,7 @@ namespace Avalonia.Gtk3 private object _lock = new object(); private IDeferredRenderOperation _nextRenderOperation; private readonly AutoResetEvent _canSetNextOperation = new AutoResetEvent(true); - + internal IntPtr? GdkWindowHandle; public WindowBaseImpl(GtkWindow gtkWidget) { @@ -55,6 +55,7 @@ namespace Avalonia.Gtk3 ConnectEvent("leave-notify-event", OnLeaveNotifyEvent); Connect("destroy", OnDestroy); Native.GtkWidgetRealize(gtkWidget); + GdkWindowHandle = this.Handle.Handle; _lastSize = ClientSize; if (Gtk3Platform.UseDeferredRendering) { diff --git a/src/Gtk/Avalonia.Gtk3/X11.cs b/src/Gtk/Avalonia.Gtk3/X11.cs new file mode 100644 index 0000000000..6708ece17b --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/X11.cs @@ -0,0 +1,54 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Gtk3 +{ + class X11 + { + [DllImport("libX11.so.6")] + public static extern IntPtr XOpenDisplay(IntPtr name); + + [DllImport("libX11.so.6")] + public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc); + + [DllImport("libX11.so.6")] + public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values); + + [DllImport("libX11.so.6")] + public static extern int XInitImage(ref XImage image); + + [DllImport("libX11.so.6")] + public static extern int XDestroyImage(ref XImage image); + + [DllImport("libX11.so.6")] + public static extern IntPtr XSetErrorHandler(XErrorHandler handler); + + public delegate int XErrorHandler(IntPtr display, IntPtr error); + + [DllImport("libX11.so.6")] + public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image, + int srcx, int srcy, int destx, int desty, uint width, uint height); + + + public unsafe struct XImage + { + public int width, height; /* size of image */ + public int xoffset; /* number of pixels offset in X direction */ + public int format; /* XYBitmap, XYPixmap, ZPixmap */ + public IntPtr data; /* pointer to image data */ + public int byte_order; /* data byte order, LSBFirst, MSBFirst */ + public int bitmap_unit; /* quant. of scanline 8, 16, 32 */ + public int bitmap_bit_order; /* LSBFirst, MSBFirst */ + public int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ + public int depth; /* depth of image */ + public int bytes_per_line; /* accelerator to next scanline */ + public int bits_per_pixel; /* bits per pixel (ZPixmap) */ + public ulong red_mask; /* bits in z arrangement */ + public ulong green_mask; + public ulong blue_mask; + private fixed byte funcs[128]; + } + + + } +} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs b/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs new file mode 100644 index 0000000000..2bf08bddf1 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Platform; + +namespace Avalonia.Gtk3 +{ + class X11Framebuffer : ILockedFramebuffer + { + private readonly IntPtr _display; + private readonly IntPtr _xid; + private IUnmanagedBlob _blob; + + public X11Framebuffer(IntPtr display, IntPtr xid, int width, int height, int factor) + { + _display = display; + _xid = xid; + Width = width*factor; + Height = height*factor; + RowBytes = Width * 4; + Dpi = new Vector(96, 96) * factor; + Format = PixelFormat.Bgra8888; + _blob = AvaloniaLocator.Current.GetService().AllocBlob(RowBytes * Height); + Address = _blob.Address; + } + + public void Dispose() + { + var image = new X11.XImage(); + int bitsPerPixel = 32; + image.width = Width; + image.height = Height; + image.format = 2; //ZPixmap; + image.data = Address; + image.byte_order = 0;// LSBFirst; + image.bitmap_unit = bitsPerPixel; + image.bitmap_bit_order = 0;// LSBFirst; + image.bitmap_pad = bitsPerPixel; + image.depth = 24; + image.bytes_per_line = RowBytes - Width * 4; + image.bits_per_pixel = bitsPerPixel; + X11.XInitImage(ref image); + var gc = X11.XCreateGC(_display, _xid, 0, IntPtr.Zero); + X11.XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint) Width, (uint) Height); + X11.XFreeGC(_display, gc); + _blob.Dispose(); + } + + public IntPtr Address { get; } + public int Width { get; } + public int Height { get; } + public int RowBytes { get; } + public Vector Dpi { get; } + public PixelFormat Format { get; } + } +} \ No newline at end of file From ecc79607d7e45b205747cc1dcdde1be3b0f2ac59 Mon Sep 17 00:00:00 2001 From: Artjom Date: Thu, 5 Oct 2017 21:29:35 +0300 Subject: [PATCH 047/482] Update readme.md The first time I've seen Avalonia's readme, I was a bit confused about how to quickly get started with the framework. So this readme update includes a new paragraph named "Getting Started" where I've tried to point out how to create a simple "Hello world" application! --- readme.md | 57 ++++++++++++++++++------------------------------------- 1 file changed, 18 insertions(+), 39 deletions(-) diff --git a/readme.md b/readme.md index 3f4840fce2..3fce3719db 100644 --- a/readme.md +++ b/readme.md @@ -1,70 +1,49 @@ # Avalonia - | Gitter Chat | Windows Build Status | Linux/Mac Build Status | |---|---|---| | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) | -A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android. +A multi-platform .NET UI framework. It runs on Windows, Linux, Mac OS X, iOS and Android. -[![](docs/images/screen.png)](https://youtu.be/wHcB3sGLVYg) +| Control catalog | Desktop platforms | Mobile platforms | +|---|---|---| +| | | | -Desktop platforms: +## About -![](docs/images/avalonia-video.png) +Avalonia is a multi-platform windowing toolkit - somewhat like WPF - that is intended to be multi-platform. It supports XAML, lookless controls and a flexible styling system, and runs on Windows using Direct2D and other operating systems using Skia and OS-specific windowing backend (GTK, Cocoa, etc). -Mobile platforms: +Avalonia is now in alpha. What does "alpha" mean? Well, it means that it's now at a stage where you can have a play and hopefully create simple applications. There's still a lot missing, and you *will* find bugs, and the API *will* change, but this represents the first time where we've made it somewhat easy to have a play and experiment with the framework. -![](https://i.ytimg.com/vi/NJ9-hnmUbBM/hqdefault.jpg) +## Getting Started -## NuGet +Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started. After installing it, open "New Project" dialog in Visual Studio, choose "Avalonia" in "Visual C#" section and press OK (screenshot). Now you can write code and markup that will work on multiple platforms! -Avalonia is delivered as a NuGet package. -You can find the packages here: ([stable(ish)](https://www.nuget.org/packages/Avalonia/), [nightly](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed)) +Avalonia is also delivered as a NuGet package. You can find the packages here: ([stable(ish)](https://www.nuget.org/packages/Avalonia/), [nightly](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed)) -You can install the package like this: -`Install-Package Avalonia -Pre` +Use this command in Package Manager console to install the package: +``` +Install-Package Avalonia -Pre +``` ## Bleeding Edge Builds Try out the latest build of Avalonia available for download here: https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master/artifacts -Try out the ControlCatalog to give it a quick demo. - -## Background - -Avalonia is a multi-platform windowing toolkit - somewhat like WPF - that is intended to be multi- -platform. It supports XAML, lookless controls and a flexible styling system, and runs on Windows -using Direct2D and other operating systems using Skia and OS-specific windowing backend (GTK, Cocoa, etc). - -## Current Status - -Avalonia is now in alpha. What does "alpha" mean? Well, it means that it's now at a stage where you -can have a play and hopefully create simple applications. There's now a [Visual -Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) -containing project and item templates that will help you get started, and -there's an initial complement of controls. There's still a lot missing, and you -*will* find bugs, and the API *will* change, but this represents the first time -where we've made it somewhat easy to have a play and experiment with the -framework. - ## Documentation -As mentioned above, Avalonia is still in alpha and as such there's not much documentation yet. You can -take a look at the [getting started page](docs/tutorial/gettingstarted.md) for an -overview of how to get started but probably the best thing to do for now is to already know a little bit -about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia). +As mentioned above, Avalonia is still in alpha and as such there's not much documentation yet. You can take a look at the [getting started page](docs/tutorial/gettingstarted.md) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia). -There's also a high-level [architecture document](docs/spec/architecture.md) that is currently a little bit -out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/. +There's also a high-level [architecture document](docs/spec/architecture.md) that is currently a little bit out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/. Contributions are always welcome! ## Building and Using -See the [build instructions here](docs/guidelines/build.md) +See the [build instructions here](docs/guidelines/build.md). -## Contributing ## +## Contributing Please read the [contribution guidelines](docs/guidelines/contributing.md) before submitting a pull request. From 5efae9e9c33b105649e185912b6ca931a4fbba53 Mon Sep 17 00:00:00 2001 From: Artjom Date: Fri, 6 Oct 2017 00:44:01 +0300 Subject: [PATCH 048/482] Update readme.md Better description! --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 3fce3719db..444071178f 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ A multi-platform .NET UI framework. It runs on Windows, Linux, Mac OS X, iOS and ## About -Avalonia is a multi-platform windowing toolkit - somewhat like WPF - that is intended to be multi-platform. It supports XAML, lookless controls and a flexible styling system, and runs on Windows using Direct2D and other operating systems using Skia and OS-specific windowing backend (GTK, Cocoa, etc). +Avalonia is a WPF-inspired cross-platform XAML UI providing a flexible styling system and supporting a wide range of OSs: Windows (.NET Framework, .NET Core), Android, Linux (GTK), MacOS and iOS. Avalonia is now in alpha. What does "alpha" mean? Well, it means that it's now at a stage where you can have a play and hopefully create simple applications. There's still a lot missing, and you *will* find bugs, and the API *will* change, but this represents the first time where we've made it somewhat easy to have a play and experiment with the framework. From 615ad42a6217351fd5838495cc07c18cd1947821 Mon Sep 17 00:00:00 2001 From: Artjom Date: Fri, 6 Oct 2017 00:45:54 +0300 Subject: [PATCH 049/482] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 444071178f..d7e90a4a44 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ A multi-platform .NET UI framework. It runs on Windows, Linux, Mac OS X, iOS and ## About -Avalonia is a WPF-inspired cross-platform XAML UI providing a flexible styling system and supporting a wide range of OSs: Windows (.NET Framework, .NET Core), Android, Linux (GTK), MacOS and iOS. +Avalonia is a WPF-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of OSs: Windows (.NET Framework, .NET Core), Android, Linux (GTK), MacOS and iOS. Avalonia is now in alpha. What does "alpha" mean? Well, it means that it's now at a stage where you can have a play and hopefully create simple applications. There's still a lot missing, and you *will* find bugs, and the API *will* change, but this represents the first time where we've made it somewhat easy to have a play and experiment with the framework. From c8e6bd58090763e2f6c48601f7469108a4bd1443 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 6 Oct 2017 12:41:48 +0100 Subject: [PATCH 050/482] Fix using dispatcher timer with 0 time interval causing exception. --- src/Avalonia.Controls/TreeView.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 5d1b9a1462..079e571d29 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -253,9 +253,7 @@ namespace Avalonia.Controls if (AutoScrollToSelectedItem) { - DispatcherTimer.RunOnce( - container.ContainerControl.BringIntoView, - TimeSpan.Zero); + Dispatcher.UIThread.InvokeAsync(container.ContainerControl.BringIntoView); } break; From bdbe95b9b1649ccfd7857cbe77caccec1e723028 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 6 Oct 2017 14:59:28 +0300 Subject: [PATCH 051/482] Fixed DispatcherTimer's priorities --- .../AndroidThreadingInterface.cs | 2 +- .../Platform/IPlatformThreadingInterface.cs | 3 ++- src/Avalonia.Base/Threading/Dispatcher.cs | 13 +++++++++++++ src/Avalonia.Base/Threading/DispatcherTimer.cs | 18 ++++-------------- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 6 ++---- src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs | 6 ++---- .../PlatformThreadingInterface.cs | 4 ++-- .../PlatformThreadingInterface.cs | 2 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- .../Avalonia.iOS/PlatformThreadingInterface.cs | 2 +- .../AvaloniaObjectTests_Threading.cs | 2 +- tests/Avalonia.RenderTests/TestBase.cs | 2 +- 12 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs index 2e5b2902f4..77dfc60b83 100644 --- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs +++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs @@ -30,7 +30,7 @@ namespace Avalonia.Android return; } - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { if (interval.TotalMilliseconds < 10) interval = TimeSpan.FromMilliseconds(10); diff --git a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs index 68f9e2c631..9f5417ca95 100644 --- a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs @@ -17,10 +17,11 @@ namespace Avalonia.Platform /// /// Starts a timer. /// + /// /// The interval. /// The action to call on each tick. /// An used to stop the timer. - IDisposable StartTimer(TimeSpan interval, Action tick); + IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick); void Signal(DispatcherPriority priority); diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index a60b663bed..a4b1aaafbc 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -84,6 +84,19 @@ namespace Avalonia.Threading _jobRunner?.Post(action, priority); } + /// + /// This is needed for platform backends that don't have internal priority system (e. g. win32) + /// To ensure that there are no jobs with higher priority + /// + /// + internal void EnsurePriority(DispatcherPriority currentPriority) + { + if (currentPriority == DispatcherPriority.MaxValue) + return; + currentPriority += 1; + _jobRunner.RunJobs(currentPriority); + } + /// /// Allows unit tests to change the platform threading interface. /// diff --git a/src/Avalonia.Base/Threading/DispatcherTimer.cs b/src/Avalonia.Base/Threading/DispatcherTimer.cs index 972fdb2049..4a8a9d673f 100644 --- a/src/Avalonia.Base/Threading/DispatcherTimer.cs +++ b/src/Avalonia.Base/Threading/DispatcherTimer.cs @@ -17,13 +17,11 @@ namespace Avalonia.Threading private readonly DispatcherPriority _priority; private TimeSpan _interval; - - private readonly Action _raiseTickAction; - + /// /// Initializes a new instance of the class. /// - public DispatcherTimer() : this(DispatcherPriority.Normal) + public DispatcherTimer() : this(DispatcherPriority.Background) { } @@ -34,7 +32,6 @@ namespace Avalonia.Threading public DispatcherTimer(DispatcherPriority priority) { _priority = priority; - _raiseTickAction = RaiseTick; } /// @@ -187,7 +184,7 @@ namespace Avalonia.Threading throw new Exception("Could not start timer: IPlatformThreadingInterface is not registered."); } - _timer = threading.StartTimer(Interval, InternalTick); + _timer = threading.StartTimer(_priority, Interval, InternalTick); } } @@ -210,14 +207,7 @@ namespace Avalonia.Threading /// private void InternalTick() { - Dispatcher.UIThread.InvokeAsync(_raiseTickAction, _priority); - } - - /// - /// Raises the event. - /// - private void RaiseTick() - { + Dispatcher.UIThread.EnsurePriority(_priority); Tick?.Invoke(this, EventArgs.Empty); } } diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 10405037ab..aefd873155 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -70,15 +70,13 @@ namespace Avalonia.Gtk3 Native.GtkMainIteration(); } - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { var msec = interval.TotalMilliseconds; - if (msec <= 0) - throw new ArgumentException("Don't know how to create a timer with zero or negative interval"); var imsec = (uint) msec; if (imsec == 0) imsec = 1; - return GlibTimeout.StarTimer(imsec, tick); + return GlibTimeout.StartTimer(GlibPriority.FromDispatcherPriority(priority), imsec, tick); } private bool[] _signaled = new bool[(int) DispatcherPriority.MaxValue + 1]; diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs index 5253be5afb..0ab4ef980c 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs @@ -48,12 +48,10 @@ namespace Avalonia.Gtk3.Interop } } - public static IDisposable StarTimer(uint interval, Action tick) + public static IDisposable StartTimer(int priority, uint interval, Action tick) { - if (interval == 0) - throw new ArgumentException("Don't know how to create a timer with zero or negative interval"); var timer = new Timer (); - GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Normal), interval, + GlibTimeout.Add(priority, interval, () => { if (timer.Stopped) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs b/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs index 3aef6944af..e6d21fca36 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs @@ -17,7 +17,7 @@ namespace Avalonia.LinuxFramebuffer public PlatformThreadingInterface() { TlsCurrentThreadIsLoopThread = true; - StartTimer(new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs())); + StartTimer(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs())); } private readonly AutoResetEvent _signaled = new AutoResetEvent(false); @@ -74,7 +74,7 @@ namespace Avalonia.LinuxFramebuffer } } - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { return new WatTimer(new System.Threading.Timer(delegate { diff --git a/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs b/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs index 80c854f5a5..184416e77a 100644 --- a/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs +++ b/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs @@ -16,7 +16,7 @@ namespace Avalonia.MonoMac public event Action Signaled; - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) => NSTimer.CreateRepeatingScheduledTimer(interval, () => tick()); public void Signal(DispatcherPriority prio) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index a260efd9b9..e265749249 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -114,7 +114,7 @@ namespace Avalonia.Win32 } } - public IDisposable StartTimer(TimeSpan interval, Action callback) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action callback) { UnmanagedMethods.TimerProc timerDelegate = (hWnd, uMsg, nIDEvent, dwTime) => callback(); diff --git a/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs b/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs index 6d6a5e22ca..43a620cccd 100644 --- a/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs +++ b/src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs @@ -51,7 +51,7 @@ namespace Avalonia.iOS } }*/ - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) => NSTimer.CreateRepeatingScheduledTimer(interval, _ => tick()); public void Signal(DispatcherPriority prio) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs index 229a34643d..09aedbdf9c 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs @@ -160,7 +160,7 @@ namespace Avalonia.Base.UnitTests throw new NotImplementedException(); } - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 409870ed0f..84860eefdb 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -161,7 +161,7 @@ namespace Avalonia.Direct2D1.RenderTests throw new NotImplementedException(); } - public IDisposable StartTimer(TimeSpan interval, Action tick) + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { throw new NotImplementedException(); } From cc7098583e420ca70198fda7bf916eb74c1731af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Fri, 6 Oct 2017 16:57:32 +0100 Subject: [PATCH 052/482] Fixed Assert.Null warnings. --- .../Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs | 2 +- .../Generators/ItemContainerGeneratorTests.cs | 2 +- .../Presenters/ContentPresenterTests_InTemplate.cs | 4 ++-- .../Presenters/ContentPresenterTests_Standalone.cs | 2 +- .../Primitives/SelectingItemsControlTests.cs | 8 ++++---- .../Primitives/SelectingItemsControlTests_Multiple.cs | 2 +- .../Data/BindingTests_RelativeSource.cs | 2 +- .../Templates/MemberSelectorTests.cs | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 05339c43b0..e9cb2bf450 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -187,7 +187,7 @@ namespace Avalonia.Base.UnitTests source.OnNext(45); - Assert.Equal(null, target.Foo); + Assert.Null(target.Foo); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs index 01b550fb3b..9b4be59647 100644 --- a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs @@ -66,7 +66,7 @@ namespace Avalonia.Controls.UnitTests.Generators target.Dematerialize(1, 1); Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); - Assert.Equal(null, target.ContainerFromIndex(1)); + Assert.Null(target.ContainerFromIndex(1)); Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2)); } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs index e32c703409..a524ca3e89 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs @@ -107,7 +107,7 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Content = child; target.Content = null; - Assert.Equal(null, child.GetLogicalParent()); + Assert.Null(child.GetLogicalParent()); Assert.Empty(target.GetLogicalChildren()); } @@ -120,7 +120,7 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Content = child; target.Content = null; - Assert.Equal(null, child.GetVisualParent()); + Assert.Null(child.GetVisualParent()); Assert.Empty(target.GetVisualChildren()); } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs index 589b1d67d2..032928d673 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs @@ -191,7 +191,7 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Content = "bar"; target.UpdateChild(); - Assert.Equal(null, foo.Parent); + Assert.Null(foo.Parent); logicalChildren = target.GetLogicalChildren(); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 7ee9fbbf52..a60074fa43 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -171,7 +171,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.SelectedItem = new Item(); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); Assert.Equal(-1, target.SelectedIndex); } @@ -278,7 +278,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.Items = null; - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); Assert.Equal(-1, target.SelectedIndex); } @@ -305,7 +305,7 @@ namespace Avalonia.Controls.UnitTests.Primitives items.RemoveAt(1); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); Assert.Equal(-1, target.SelectedIndex); } @@ -334,7 +334,7 @@ namespace Avalonia.Controls.UnitTests.Primitives items.Clear(); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); Assert.Equal(-1, target.SelectedIndex); } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index d8600f472d..642f594e4d 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -76,7 +76,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.SelectedItems = new AvaloniaList(); Assert.Equal(-1, target.SelectedIndex); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); } [Fact] diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs index e912770470..c46fb6fce2 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs @@ -117,7 +117,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data }; target.Bind(TextBox.TextProperty, binding); - Assert.Equal(null, target.Text); + Assert.Null(target.Text); } [Fact] diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs index 49a88e8fae..aa1e56f2a5 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs @@ -63,7 +63,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates { var selector = new MemberSelector() { MemberName = "StringValue" }; - Assert.Equal(null, selector.Select(null)); + Assert.Null(selector.Select(null)); } [Fact] @@ -73,7 +73,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates var data = new Item() { StringValue = "Value1" }; - Assert.Same(null, selector.Select(data)); + Assert.Null(selector.Select(data)); } [Fact] From 2b8953c7abbd4f9b7fed59f34ac3648703c42d81 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 6 Oct 2017 19:23:14 +0300 Subject: [PATCH 053/482] Fixed merge issues and made new previewer to work with desktop .net --- Avalonia.sln | 206 +++++++++++------- packages.cake | 12 + samples/ControlCatalog.Desktop/Program.cs | 10 +- samples/ControlCatalog.NetCore/Program.cs | 17 +- .../Avalonia.Controls.csproj | 2 +- src/Avalonia.Controls/Remote/RemoteServer.cs | 3 +- .../Avalonia.DesignerSupport.csproj | 2 +- .../Remote/PreviewerWindowImpl.cs | 7 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 14 ++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 2 - .../Avalonia.Designer.HostApp.NetFX.csproj | 28 +++ .../Program.cs | 14 ++ .../Properties/launchSettings.json | 8 + .../Avalonia.Designer.HostApp.csproj | 1 + 14 files changed, 222 insertions(+), 104 deletions(-) create mode 100644 src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj create mode 100644 src/tools/Avalonia.Designer.HostApp.NetFX/Program.cs create mode 100644 src/tools/Avalonia.Designer.HostApp.NetFX/Properties/launchSettings.json diff --git a/Avalonia.sln b/Avalonia.sln index 6f46bf953e..7f516667ee 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 @@ -178,19 +179,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests", "tests\Avalonia.RenderTests\Avalonia.Skia.RenderTests.csproj", "{E1582370-37B3-403C-917F-8209551B1634}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Remote.Protocol", "src\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj", "{D78A720C-C0C6-478B-8564-F167F9BDD01B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Remote.Protocol", "src\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj", "{D78A720C-C0C6-478B-8564-F167F9BDD01B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteTest", "samples\RemoteTest\RemoteTest.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteTest", "samples\RemoteTest\RemoteTest.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{4ED8B739-6F4E-4CD4-B993-545E6B5CE637}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer.HostApp", "src\tools\Avalonia.Designer.HostApp\Avalonia.Designer.HostApp.csproj", "{050CC912-FF49-4A8B-B534-9544017446DD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Designer.HostApp", "src\tools\Avalonia.Designer.HostApp\Avalonia.Designer.HostApp.csproj", "{050CC912-FF49-4A8B-B534-9544017446DD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Previewer", "samples\Previewer\Previewer.csproj", "{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Previewer", "samples\Previewer\Previewer.csproj", "{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OSX", "OSX", "{A59C4C0A-64DF-4621-B450-2BA00D6F61E2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MonoMac", "src\OSX\Avalonia.MonoMac\Avalonia.MonoMac.csproj", "{CBFD5788-567D-401B-9DFA-74E4224025A0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer.HostApp.NetFX", "src\tools\Avalonia.Designer.HostApp.NetFX\Avalonia.Designer.HostApp.NetFX.csproj", "{4ADA61C8-D191-428D-9066-EF4F0D86520F}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -200,7 +204,6 @@ Global tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{48840edd-24bf-495d-911e-2eb12ae75d3b}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{7863ea94-f0fb-4386-bf8c-e5bfa761560a}*SharedItemsImports = 4 - src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 4 src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4 src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{9defc6b7-845b-4d8f-afc0-d32bf0032b8c}*SharedItemsImports = 13 @@ -2444,86 +2447,86 @@ Global {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|Mono.Build.0 = Release|Any CPU {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|x86.ActiveCfg = Release|Any CPU {E2999E4A-9086-401F-898C-AEB0AD38E676}.Release|x86.Build.0 = Release|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.ActiveCfg = Ad-Hoc|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.Build.0 = Ad-Hoc|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|x86 - {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.Build.0 = Ad-Hoc|x86 - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.Build.0 = AppStore|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.ActiveCfg = AppStore|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.Build.0 = AppStore|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.ActiveCfg = AppStore|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.Build.0 = AppStore|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.ActiveCfg = AppStore|x86 - {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.Build.0 = AppStore|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|Mono.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Any CPU.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhone.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|Mono.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.AppStore|x86.Build.0 = Release|Any CPU {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.ActiveCfg = Debug|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.Build.0 = Debug|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.ActiveCfg = Debug|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.Build.0 = Debug|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.ActiveCfg = Debug|x86 - {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.Build.0 = Debug|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhone.Build.0 = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.ActiveCfg = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|Mono.Build.0 = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.ActiveCfg = Debug|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.Build.0 = Debug|Any CPU {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.Build.0 = Release|Any CPU - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.ActiveCfg = Release|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.Build.0 = Release|iPhone - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.ActiveCfg = Release|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.Build.0 = Release|Mono - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.ActiveCfg = Release|x86 - {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.Build.0 = Release|x86 - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.ActiveCfg = Ad-Hoc|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.Build.0 = Ad-Hoc|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|x86 - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.Build.0 = Ad-Hoc|x86 - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.Build.0 = AppStore|Any CPU - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.ActiveCfg = AppStore|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.Build.0 = AppStore|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.ActiveCfg = AppStore|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.Build.0 = AppStore|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.ActiveCfg = AppStore|x86 - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.Build.0 = AppStore|x86 + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhone.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|Mono.Build.0 = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.ActiveCfg = Release|Any CPU + {050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Mono.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Any CPU.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhone.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|Mono.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.AppStore|x86.Build.0 = Release|Any CPU {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.ActiveCfg = Debug|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.Build.0 = Debug|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.ActiveCfg = Debug|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.Build.0 = Debug|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.ActiveCfg = Debug|x86 - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.Build.0 = Debug|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhone.Build.0 = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.ActiveCfg = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|Mono.Build.0 = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.ActiveCfg = Debug|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Debug|x86.Build.0 = Debug|Any CPU {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Any CPU.Build.0 = Release|Any CPU - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.ActiveCfg = Release|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.Build.0 = Release|iPhone - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.ActiveCfg = Release|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.Build.0 = Release|Mono - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.ActiveCfg = Release|x86 - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.Build.0 = Release|x86 + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhone.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|Mono.Build.0 = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.ActiveCfg = Release|Any CPU + {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Release|x86.Build.0 = Release|Any CPU {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU @@ -2564,6 +2567,46 @@ Global {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Mono.Build.0 = Release|Any CPU {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|x86.ActiveCfg = Release|Any CPU {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|x86.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|Mono.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|Any CPU.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|iPhone.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|Mono.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|Mono.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|x86.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.AppStore|x86.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|iPhone.Build.0 = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|Mono.ActiveCfg = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|Mono.Build.0 = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|x86.ActiveCfg = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Debug|x86.Build.0 = Debug|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|Any CPU.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|iPhone.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|iPhone.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|Mono.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|Mono.Build.0 = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|x86.ActiveCfg = Release|Any CPU + {4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2620,12 +2663,7 @@ Global {E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098} {050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1E8CA5AA-707A-4C57-A682-D265A49E10C3} - {050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} - {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098} - {CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2} + {4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/packages.cake b/packages.cake index a22ff65e72..98aa08bb50 100644 --- a/packages.cake +++ b/packages.cake @@ -223,6 +223,17 @@ public class Packages }; }); + var toolsContent = new[] { + new NuSpecContent{ + Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp/bin/" + parameters.DirSuffix + "/netcoreapp2.0/Avalonia.Designer.HostApp.dll")).FullPath, + Target = "tools/netcoreapp2.0/previewer" + }, + new NuSpecContent{ + Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/net461/Avalonia.Designer.HostApp.NetFx.exe")).FullPath, + Target = "tools/net461/previewer" + } + }; + var nuspecNuGetSettingsCore = new [] { /////////////////////////////////////////////////////////////////////////////// @@ -253,6 +264,7 @@ public class Packages Files = coreLibrariesNuSpecContent .Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform) .Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform) + .Concat(toolsContent) .ToList(), BasePath = context.Directory("./"), OutputDirectory = parameters.NugetRoot diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index b67c5ea51d..20bf4ed342 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -16,11 +16,15 @@ namespace ControlCatalog // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. - AppBuilder.Configure() - .UsePlatformDetect() - .Start(); + BuildAvaloniaApp().Start(); } + /// + /// This method is needed for IDE previewer infrastructure + /// + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure().UsePlatformDetect(); + // This will be made into a runtime configuration extension soon! private static void InitializeLogging() { diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 640dce0a80..346535d39d 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -28,19 +28,14 @@ namespace ControlCatalog.NetCore System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); }); else - AppBuilder.Configure() - .CustomPlatformDetect() - .UseReactiveUI() - .Start(); + BuildAvaloniaApp().Start(); } - static AppBuilder CustomPlatformDetect(this AppBuilder builder) - { - //This is needed because we still aren't ready to have MonoMac backend as default one - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return builder.UseSkia().UseMonoMac(); - return builder.UsePlatformDetect(); - } + /// + /// This method is needed for IDE previewer infrastructure + /// + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure().UsePlatformDetect().UseReactiveUI(); static void ConsoleSilencer() { diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 0c9981f209..c8f6b4e65a 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -12,7 +12,7 @@ prompt 4 bin\Debug\Avalonia.Controls.XML - CS1591 + CS1591;CS0067 pdbonly diff --git a/src/Avalonia.Controls/Remote/RemoteServer.cs b/src/Avalonia.Controls/Remote/RemoteServer.cs index 0bc43069e7..86f8318e15 100644 --- a/src/Avalonia.Controls/Remote/RemoteServer.cs +++ b/src/Avalonia.Controls/Remote/RemoteServer.cs @@ -19,8 +19,9 @@ namespace Avalonia.Controls.Remote public TopLevelImpl(IAvaloniaRemoteTransportConnection transport) : base(transport) { } - +#pragma warning disable 67 public event Action LostFocus; + } public RemoteServer(IAvaloniaRemoteTransportConnection transport) diff --git a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj index 63cb8132a9..9f54137e47 100644 --- a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj +++ b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj @@ -12,7 +12,7 @@ prompt 4 bin\Debug\Avalonia.DesignerSupport.xml - CS1591 + CS1591;CS0067 pdbonly diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index a787c8e8e3..535dfd700b 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -65,7 +65,9 @@ namespace Avalonia.DesignerSupport.Remote ClientSize = clientSize; RenderIfNeeded(); } - + + public IScreenImpl Screen { get; } = new ScreenStub(); + public void Activate() { } @@ -87,5 +89,8 @@ namespace Avalonia.DesignerSupport.Remote { } + public void ShowTaskbarIcon(bool value) + { + } } } \ No newline at end of file diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 5aad114cf7..2ed434a2dc 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -75,6 +75,8 @@ namespace Avalonia.DesignerSupport.Remote { } + public IScreenImpl Screen { get; } = new ScreenStub(); + public void SetTitle(string title) { } @@ -88,6 +90,10 @@ namespace Avalonia.DesignerSupport.Remote public void SetIcon(IWindowIconImpl icon) { } + + public void ShowTaskbarIcon(bool value) + { + } } class ClipboardStub : IClipboard @@ -129,4 +135,12 @@ namespace Avalonia.DesignerSupport.Remote public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) => Task.FromResult((string) null); } + + class ScreenStub : IScreenImpl + { + public int ScreenCount => 1; + + public Screen[] AllScreens { get; } = + {new Screen(new Rect(0, 0, 4000, 4000), new Rect(0, 0, 4000, 4000), true)}; + } } \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index ab1bf4eb34..15b3a11fbb 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -333,8 +333,6 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public delegate ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data, IntPtr destroy); - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] - public delegate ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data, IntPtr destroy); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public delegate ulong g_free(IntPtr data); diff --git a/src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj b/src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj new file mode 100644 index 0000000000..cb4ce3f301 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp.NetFX/Avalonia.Designer.HostApp.NetFX.csproj @@ -0,0 +1,28 @@ + + + Exe + net461 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp.NetFX/Program.cs b/src/tools/Avalonia.Designer.HostApp.NetFX/Program.cs new file mode 100644 index 0000000000..d3f80dd621 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp.NetFX/Program.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Designer.HostApp.NetFX +{ + class Program + { + public static void Main(string[] args) + => Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(args); + } +} diff --git a/src/tools/Avalonia.Designer.HostApp.NetFX/Properties/launchSettings.json b/src/tools/Avalonia.Designer.HostApp.NetFX/Properties/launchSettings.json new file mode 100644 index 0000000000..493caa2693 --- /dev/null +++ b/src/tools/Avalonia.Designer.HostApp.NetFX/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Avalonia.Designer.HostApp.NetFX": { + "commandName": "Project", + "commandLineArgs": "--transport tcp-bson://127.0.0.1:25000/ bin/Debug/net461/ControlCatalog.Desktop.exe" + } + } +} \ No newline at end of file diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 2a68862f18..5bb4b9dd4a 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -2,6 +2,7 @@ Exe netcoreapp2.0 + From 3e09457e10e058aed23db2090afaafaa79b0d1b5 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 6 Oct 2017 20:22:52 +0300 Subject: [PATCH 054/482] [D2D1] Implement support for IFramebufferPlatformSurface --- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 2 + .../FramebufferShimRenderTarget.cs | 84 ++++++++++++++++++ .../Media/Imaging/RenderTargetBitmapImpl.cs | 13 ++- .../Media/Imaging/WicBitmapImpl.cs | 1 + .../Avalonia.RenderTests/Media/BitmapTests.cs | 11 +-- ...ouldBeUsableAsBitmap_Bgra8888.expected.png | Bin 0 -> 800 bytes ...ShouldBeUsableAsBitmap_Rgb565.expected.png | Bin 0 -> 786 bytes ...ouldBeUsableAsBitmap_Rgba8888.expected.png | Bin 0 -> 800 bytes 8 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs create mode 100644 tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index fd8364c03b..90409bb208 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -146,6 +146,8 @@ namespace Avalonia.Direct2D1 } if (s is IExternalDirect2DRenderTargetSurface external) return new ExternalRenderTarget(external, s_dwfactory); + if (s is IFramebufferPlatformSurface fb) + return new FramebufferShimRenderTarget(fb, s_imagingFactory, s_d2D1Factory, s_dwfactory); } throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); } diff --git a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs new file mode 100644 index 0000000000..d465af5322 --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media.Imaging; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.Win32.Interop; +using SharpDX.Direct2D1; +using SharpDX.WIC; +using PixelFormat = Avalonia.Platform.PixelFormat; + +namespace Avalonia.Direct2D1 +{ + class FramebufferShimRenderTarget : IRenderTarget + { + private readonly IFramebufferPlatformSurface _surface; + private readonly ImagingFactory _imagingFactory; + private readonly Factory _d2DFactory; + private readonly SharpDX.DirectWrite.Factory _dwriteFactory; + + public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface, + ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory) + { + _surface = surface; + _imagingFactory = imagingFactory; + _d2DFactory = d2dFactory; + _dwriteFactory = dwriteFactory; + } + + public void Dispose() + { + + } + + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + var locked = _surface.Lock(); + if (locked.Format == PixelFormat.Rgb565) + { + locked.Dispose(); + throw new ArgumentException("Unsupported pixel format: " + locked.Format); + } + + return new FramebufferShim(locked, _imagingFactory, _d2DFactory, _dwriteFactory) + .CreateDrawingContext(visualBrushRenderer); + } + + class FramebufferShim : RenderTargetBitmapImpl + { + private readonly ILockedFramebuffer _target; + + public FramebufferShim(ILockedFramebuffer target, + ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory + ) : base(imagingFactory, d2dFactory, dwriteFactory, + target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format) + { + _target = target; + } + + public override IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + return base.CreateDrawingContext(visualBrushRenderer, () => + { + using (var l = WicImpl.Lock(BitmapLockFlags.Read)) + { + for (var y = 0; y < _target.Height; y++) + { + UnmanagedMethods.CopyMemory( + _target.Address + _target.RowBytes * y, + l.Data.DataPointer + l.Stride * y, + (uint) Math.Min(l.Stride, _target.RowBytes)); + } + } + Dispose(); + _target.Dispose(); + + }); + } + } + + } +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs index 33736b02cb..0d6ed9f39f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs @@ -22,8 +22,9 @@ namespace Avalonia.Direct2D1.Media int width, int height, double dpiX, - double dpiY) - : base(imagingFactory, width, height) + double dpiY, + Platform.PixelFormat? pixelFormat = null) + : base(imagingFactory, width, height, pixelFormat) { var props = new RenderTargetProperties { @@ -45,9 +46,13 @@ namespace Avalonia.Direct2D1.Media base.Dispose(); } - public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + public virtual IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + => CreateDrawingContext(visualBrushRenderer, null); + + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback) { - return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory); + return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory, + finishedCallback: finishedCallback); } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index e817dd4812..bcce2496cd 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -74,6 +74,7 @@ namespace Avalonia.Direct2D1.Media public WicBitmapImpl(ImagingFactory factory, Platform.PixelFormat format, IntPtr data, int width, int height, int stride) { WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); + _factory = factory; PixelFormat = format; using (var l = WicImpl.Lock(BitmapLockFlags.Write)) { diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs index f01f78ae94..a7cd06a894 100644 --- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs +++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs @@ -64,13 +64,13 @@ namespace Avalonia.Direct2D1.RenderTests.Media public void Deallocate() => Marshal.FreeHGlobal(Address); } - -#if AVALONIA_SKIA + [Theory] -#else - [Theory(Skip = "Framebuffer not supported")] + [InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888), +#if SKIA + InlineData(PixelFormat.Rgb565) #endif - [InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgb565)] + ] public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt) { var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt; @@ -84,6 +84,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media ctx.FillRectangle(Brushes.Chartreuse, new Rect(0, 0, 20, 100)); ctx.FillRectangle(Brushes.Crimson, new Rect(20, 0, 20, 100)); ctx.FillRectangle(Brushes.Gold, new Rect(40, 0, 20, 100)); + ctx.PopOpacity(); } var bmp = new Bitmap(fmt, fb.Address, fb.Width, fb.Height, fb.RowBytes); diff --git a/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png b/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..19686464c546d3f56a55e15fc4df8bc574ecf13e GIT binary patch literal 800 zcmeAS@N?(olHy`uVBq!ia0vp^DIm0?E|u$lp4Sznk+pCI zt8&hw6|IZJx}ppYsSSFePk-;<{yg#k=k4>?e|`Nut9(N0JB8DC9H;GU)+uI> ze8#uwjBLtG>k~#GiPBBIAg^g;9psn|@-5J#JsUblxX|hXn_`Q3mNMsM`qKH18@E56 ndHCa*hZWCgD4b?9|371-;u7va=|XQ{s%G$X^>bP0l+XkKg^yP@ literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png b/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d20008a1bde3d523b8be3c2dc29af79b52303f GIT binary patch literal 786 zcmeAS@N?(olHy`uVBq!ia0vp^DIm_*Z1E)2cG}@ZrO^~MPgl128YzTqBOD= zu3%NpSu{k1j{g3hz18}JQTc?_cM7NPI8NKytW(S$`HXMV8QGMXK#BYB6l%hUjCbf5 z@>(+IW%|o_YA=nTHk63T%ok=2_BESn~aE`Pr+P_hqe@Y6hlb22WQ%mvv4F FO#n8jN}B)x literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png b/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..19686464c546d3f56a55e15fc4df8bc574ecf13e GIT binary patch literal 800 zcmeAS@N?(olHy`uVBq!ia0vp^DIm0?E|u$lp4Sznk+pCI zt8&hw6|IZJx}ppYsSSFePk-;<{yg#k=k4>?e|`Nut9(N0JB8DC9H;GU)+uI> ze8#uwjBLtG>k~#GiPBBIAg^g;9psn|@-5J#JsUblxX|hXn_`Q3mNMsM`qKH18@E56 ndHCa*hZWCgD4b?9|371-;u7va=|XQ{s%G$X^>bP0l+XkKg^yP@ literal 0 HcmV?d00001 From abe47da717f1269e0d62fa84e0e187a97c7a7fa3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 7 Oct 2017 19:05:51 +0200 Subject: [PATCH 055/482] Fix flicker on resize under D2D. Still getting a "rubber banding" effect that needs to be fixed though. --- .../Rendering/DeferredRenderer.cs | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 81a2998fd2..5434a35464 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -185,36 +185,40 @@ namespace Avalonia.Rendering try { - using (var context = _renderTarget.CreateDrawingContext(this)) + if (scene != null && scene.Size != Size.Empty) { - if (scene != null && scene.Size != Size.Empty) + IDrawingContextImpl context = null; + + if (scene.Generation != _lastSceneId) { - if (scene.Generation != _lastSceneId) - { - _layers.Update(scene, context); + context = _renderTarget.CreateDrawingContext(this); + _layers.Update(scene, context); - RenderToLayers(scene); + RenderToLayers(scene); - if (DebugFramesPath != null) - { - SaveDebugFrames(scene.Generation); - } + if (DebugFramesPath != null) + { + SaveDebugFrames(scene.Generation); + } - _lastSceneId = scene.Generation; + _lastSceneId = scene.Generation; - composite = true; - } + composite = true; + } - if (renderOverlay) - { - RenderOverlay(scene, context); - RenderComposite(scene, context); - } - else if (composite) - { - RenderComposite(scene, context); - } + if (renderOverlay) + { + context = context ?? _renderTarget.CreateDrawingContext(this); + RenderOverlay(scene, context); + RenderComposite(scene, context); + } + else if (composite) + { + context = context ?? _renderTarget.CreateDrawingContext(this); + RenderComposite(scene, context); } + + context?.Dispose(); } } catch (RenderTargetCorruptedException ex) From e2b9e5dbaa46af922b062906b829eb23a7f50b84 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 7 Oct 2017 21:15:45 +0300 Subject: [PATCH 056/482] Call VerifyAccess in AvaloniaObject.ctor --- src/Avalonia.Base/AvaloniaObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 7defcfbe6a..efcbb57244 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -56,7 +56,7 @@ namespace Avalonia /// public AvaloniaObject() { - CheckAccess(); + VerifyAccess(); foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this)) { object value = property.IsDirect ? @@ -818,4 +818,4 @@ namespace Avalonia throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}"); } } -} \ No newline at end of file +} From 120eb2dde6d9671e712f8c456c684bda467a0c8d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 7 Oct 2017 15:00:46 -0500 Subject: [PATCH 057/482] PR Feedback. --- .../Data/Plugins/MethodAccessorPlugin.cs | 5 - .../Avalonia.Markup/DefaultValueConverter.cs | 2 +- .../Data/BindingTests_Method.cs | 106 ++++++++++++++++++ 3 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs index c602fa4f4d..db0d3e0299 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs +++ b/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs @@ -82,11 +82,6 @@ namespace Avalonia.Markup.Data.Plugins } catch { } } - - private void SendCurrentValue() - { - - } } } } diff --git a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs index bb6bd85827..28d64eb561 100644 --- a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs +++ b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs @@ -35,7 +35,7 @@ namespace Avalonia.Markup return AvaloniaProperty.UnsetValue; } - if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d) + if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1) { return new AlwaysEnabledDelegateCommand(d); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs new file mode 100644 index 0000000000..031f14d686 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs @@ -0,0 +1,106 @@ +// 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.Reactive.Subjects; +using System.Windows.Input; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Data +{ + public class BindingTests_Method + { + [Fact] + public void Binding_Method_To_Command_Works() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + +