From e5cac827b18c00e06dbe9c0088225994b7259e82 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 22 Oct 2018 09:39:52 +0200 Subject: [PATCH 001/147] Make TemplatedParent a direct property. During initial layout, `TemplatedParent` is read tens of thousands of times. This being a styled property that used `inherits: true` meant that it was showing up in profiling as taking up a significant amount of time. Make `TemplatedParent` a direct property so that reading it is a simple property access. This doesn't actually complicate the code much at all as once set the property value doesn't change, so `inherited: true` semantics are not that important. --- src/Avalonia.Controls/ItemsControl.cs | 2 -- .../Primitives/TemplatedControl.cs | 19 ++++++++++++++++++- .../Templates/TemplateExtensions.cs | 6 ++++-- src/Avalonia.Styling/StyledElement.cs | 12 ++++++++---- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 9d4cbb9260..d74078c712 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -249,8 +249,6 @@ namespace Avalonia.Controls if (containerControl != null) { ((ISetLogicalParent)containerControl).SetParent(this); - containerControl.SetValue(TemplatedParentProperty, null); - containerControl.UpdateChild(); if (containerControl.Child != null) diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 296134ca48..ba4c5027d0 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -260,7 +260,7 @@ namespace Avalonia.Controls.Primitives var child = template.Build(this); var nameScope = new NameScope(); NameScope.SetNameScope((Control)child, nameScope); - child.SetValue(TemplatedParentProperty, this); + ApplyTemplatedParent(child); RegisterNames(child, nameScope); ((ISetLogicalParent)child).SetParent(this); VisualChildren.Add(child); @@ -326,6 +326,23 @@ namespace Avalonia.Controls.Primitives InvalidateMeasure(); } + /// + /// Sets the TemplatedParent property for the created template children. + /// + /// The control. + private void ApplyTemplatedParent(IControl control) + { + control.SetValue(TemplatedParentProperty, this); + + foreach (var child in control.LogicalChildren) + { + if (child is IControl c) + { + ApplyTemplatedParent(c); + } + } + } + /// /// Registers each control with its name scope. /// diff --git a/src/Avalonia.Controls/Templates/TemplateExtensions.cs b/src/Avalonia.Controls/Templates/TemplateExtensions.cs index 09da737836..18c8bfdeda 100644 --- a/src/Avalonia.Controls/Templates/TemplateExtensions.cs +++ b/src/Avalonia.Controls/Templates/TemplateExtensions.cs @@ -24,12 +24,14 @@ namespace Avalonia.Controls.Templates { foreach (IControl child in control.GetVisualChildren()) { - if (child.TemplatedParent == templatedParent) + var childTemplatedParent = child.TemplatedParent; + + if (childTemplatedParent == templatedParent) { yield return child; } - if (child.TemplatedParent != null) + if (childTemplatedParent != null) { foreach (var descendant in GetTemplateChildren(child, templatedParent)) { diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index 3d0c840040..e52a1961ba 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -49,8 +49,11 @@ namespace Avalonia /// /// Defines the property. /// - public static readonly StyledProperty TemplatedParentProperty = - AvaloniaProperty.Register(nameof(TemplatedParent), inherits: true); + public static readonly DirectProperty TemplatedParentProperty = + AvaloniaProperty.RegisterDirect( + nameof(TemplatedParent), + o => o.TemplatedParent, + (o ,v) => o.TemplatedParent = v); private int _initCount; private string _name; @@ -62,6 +65,7 @@ namespace Avalonia private Styles _styles; private bool _styled; private Subject _styleDetach = new Subject(); + private ITemplatedControl _templatedParent; private bool _dataContextUpdating; /// @@ -269,8 +273,8 @@ namespace Avalonia /// public ITemplatedControl TemplatedParent { - get { return GetValue(TemplatedParentProperty); } - internal set { SetValue(TemplatedParentProperty, value); } + get => _templatedParent; + internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value); } /// From 44e12491eab44ca65673f83b594f1c558b68e1f8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 22 Oct 2018 09:40:08 +0200 Subject: [PATCH 002/147] Ignore Avalonia.Native in ncrunch. --- .ncrunch/Avalonia.Native.v3.ncrunchproject | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .ncrunch/Avalonia.Native.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.Native.v3.ncrunchproject b/.ncrunch/Avalonia.Native.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.Native.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file From e4838842a11024837936a1016604f42835bb6b30 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 2 Nov 2018 16:24:53 +0200 Subject: [PATCH 003/147] simplify Layout transform no need to be ContentControl --- .../LayoutTransformControl.cs | 39 +++++++------------ src/Avalonia.Themes.Default/DefaultTheme.xaml | 1 - .../LayoutTransformControl.xaml | 13 ------- .../LayoutTransformControlTests.cs | 18 ++------- 4 files changed, 17 insertions(+), 54 deletions(-) delete mode 100644 src/Avalonia.Themes.Default/LayoutTransformControl.xaml diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index 87e3853643..73d5e61e03 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -5,20 +5,17 @@ // http://silverlight.codeplex.com/SourceControl/changeset/view/74775#Release/Silverlight4/Source/Controls.Layout.Toolkit/LayoutTransformer/LayoutTransformer.cs // -using Avalonia.Controls.Primitives; -using Avalonia.Media; -using Avalonia.VisualTree; using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Reactive.Linq; +using Avalonia.Media; namespace Avalonia.Controls { /// /// Control that implements support for transformations as if applied by LayoutTransform. /// - public class LayoutTransformControl : ContentControl + public class LayoutTransformControl : Decorator { public static readonly AvaloniaProperty LayoutTransformProperty = AvaloniaProperty.Register(nameof(LayoutTransform)); @@ -27,6 +24,9 @@ namespace Avalonia.Controls { LayoutTransformProperty.Changed .AddClassHandler(x => x.OnLayoutTransformChanged); + + ChildProperty.Changed + .AddClassHandler(x => x.OnChildChanged); } /// @@ -38,8 +38,7 @@ namespace Avalonia.Controls set { SetValue(LayoutTransformProperty, value); } } - public Control TransformRoot => _transformRoot ?? - (_transformRoot = this.GetVisualChildren().OfType().FirstOrDefault()); + public IControl TransformRoot => Child; /// /// Provides the behavior for the "Arrange" pass of layout. @@ -132,16 +131,8 @@ namespace Avalonia.Controls return transformedDesiredSize; } - /// - /// Builds the visual tree for the LayoutTransformerControl when a new - /// template is applied. - /// - protected override void OnTemplateApplied(TemplateAppliedEventArgs e) + private void OnChildChanged(AvaloniaPropertyChangedEventArgs e) { - base.OnTemplateApplied(e); - - _matrixTransform = new MatrixTransform(); - if (null != TransformRoot) { TransformRoot.RenderTransform = _matrixTransform; @@ -169,14 +160,14 @@ namespace Avalonia.Controls /// /// RenderTransform/MatrixTransform applied to TransformRoot. /// - private MatrixTransform _matrixTransform; + private MatrixTransform _matrixTransform = new MatrixTransform(); /// /// Transformation matrix corresponding to _matrixTransform. /// private Matrix _transformation; private IDisposable _transformChangedEvent = null; - private Control _transformRoot; + /// /// Returns true if Size a is smaller than Size b in either dimension. /// @@ -215,7 +206,8 @@ namespace Avalonia.Controls /// private void ApplyLayoutTransform() { - if (LayoutTransform == null) return; + if (LayoutTransform == null) + return; // Get the transform matrix and apply it _transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound); @@ -376,11 +368,8 @@ namespace Avalonia.Controls { var newTransform = e.NewValue as Transform; - if (_transformChangedEvent != null) - { - _transformChangedEvent.Dispose(); - _transformChangedEvent = null; - } + _transformChangedEvent?.Dispose(); + _transformChangedEvent = null; if (newTransform != null) { @@ -392,4 +381,4 @@ namespace Avalonia.Controls ApplyLayoutTransform(); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 2b9132ee56..16705f91c2 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -12,7 +12,6 @@ - diff --git a/src/Avalonia.Themes.Default/LayoutTransformControl.xaml b/src/Avalonia.Themes.Default/LayoutTransformControl.xaml deleted file mode 100644 index b26f053622..0000000000 --- a/src/Avalonia.Themes.Default/LayoutTransformControl.xaml +++ /dev/null @@ -1,13 +0,0 @@ - \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs b/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs index d5f9818f89..13c946b549 100644 --- a/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs @@ -1,6 +1,4 @@ -using Avalonia.Controls.Presenters; using Avalonia.Controls.Shapes; -using Avalonia.Controls.Templates; using Avalonia.Media; using Xunit; @@ -311,20 +309,10 @@ namespace Avalonia.Controls.UnitTests { var lt = new LayoutTransformControl() { - LayoutTransform = transform, - Template = new FuncControlTemplate( - p => new ContentPresenter() { Content = p.Content }) + LayoutTransform = transform }; - lt.Content = new Rectangle() { Width = width, Height = height }; - - lt.ApplyTemplate(); - - //we need to force create visual child - //so the measure after is correct - (lt.Presenter as ContentPresenter).UpdateChild(); - - Assert.NotNull(lt.Presenter?.Child); + lt.Child = new Rectangle() { Width = width, Height = height }; lt.Measure(Size.Infinity); lt.Arrange(new Rect(lt.DesiredSize)); @@ -332,4 +320,4 @@ namespace Avalonia.Controls.UnitTests return lt; } } -} \ No newline at end of file +} From 2b98500aaf9c2e0a86761b33695d65d27d2709cb Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 2 Nov 2018 18:00:45 +0200 Subject: [PATCH 004/147] add simple viewbox implementation --- src/Avalonia.Controls/ViewBox.cs | 123 +++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/Avalonia.Controls/ViewBox.cs diff --git a/src/Avalonia.Controls/ViewBox.cs b/src/Avalonia.Controls/ViewBox.cs new file mode 100644 index 0000000000..7492e32937 --- /dev/null +++ b/src/Avalonia.Controls/ViewBox.cs @@ -0,0 +1,123 @@ +using System; +using Avalonia.Media; + +namespace Avalonia.Controls +{ + /// + /// Viewbox is used to scale single child. + /// + /// + public class Viewbox : Decorator + { + /// + /// The stretch property + /// + public static AvaloniaProperty StretchProperty = + AvaloniaProperty.RegisterDirect(nameof(Stretch), + v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform); + + private Stretch _stretch = Stretch.Uniform; + + /// + /// Gets or sets the stretch mode, + /// which determines how child fits into the available space. + /// + /// + /// The stretch. + /// + public Stretch Stretch + { + get => _stretch; + set => SetAndRaise(StretchProperty, ref _stretch, value); + } + + static Viewbox() + { + AffectsMeasure(StretchProperty); + } + + protected override Size MeasureOverride(Size availableSize) + { + var child = Child; + + if (child != null) + { + child.Measure(Size.Infinity); + + var childSize = child.DesiredSize; + + var scale = GetScale(availableSize, childSize, Stretch); + + return childSize * scale; + } + + return new Size(); + } + + protected override Size ArrangeOverride(Size finalSize) + { + var child = Child; + + if (child != null) + { + var childSize = child.DesiredSize; + var scale = GetScale(finalSize, childSize, Stretch); + var scaleTransform = child.RenderTransform as ScaleTransform; + + if (scaleTransform == null) + { + child.RenderTransform = scaleTransform = new ScaleTransform(scale.X, scale.Y); + child.RenderTransformOrigin = RelativePoint.TopLeft; + } + + scaleTransform.ScaleX = scale.X; + scaleTransform.ScaleY = scale.Y; + + child.Arrange(new Rect(childSize)); + + return childSize * scale; + } + + return new Size(); + } + + private static Vector GetScale(Size availableSize, Size childSize, Stretch stretch) + { + double scaleX = 1.0; + double scaleY = 1.0; + + bool validWidth = !double.IsPositiveInfinity(availableSize.Width); + bool validHeight = !double.IsPositiveInfinity(availableSize.Height); + + if (stretch != Stretch.None && (validWidth || validHeight)) + { + scaleX = childSize.Width <= 0.0 ? 0.0 : availableSize.Width / childSize.Width; + scaleY = childSize.Height <= 0.0 ? 0.0 : availableSize.Height / childSize.Height; + + if (!validWidth) + { + scaleX = scaleY; + } + else if (!validHeight) + { + scaleY = scaleX; + } + else + { + switch (stretch) + { + case Stretch.Uniform: + scaleX = scaleY = Math.Min(scaleX, scaleY); + break; + + case Stretch.UniformToFill: + scaleX = scaleY = Math.Max(scaleX, scaleY); + break; + } + } + } + + return new Vector(scaleX, scaleY); + } + } +} From 37d7a49fee2edfbf4ae3c12aa54d2643e0a7c33d Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 2 Nov 2018 18:51:46 +0200 Subject: [PATCH 005/147] add some tests for Viewbox --- .../ViewboxTests.cs | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 tests/Avalonia.Controls.UnitTests/ViewboxTests.cs diff --git a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs new file mode 100644 index 0000000000..ad0f318d2f --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs @@ -0,0 +1,105 @@ +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class ViewboxTests + { + [Fact] + public void Viewbox_Stretch_Uniform_Child() + { + var target = new Viewbox() { Child = new Rectangle() { Width = 100, Height = 50 } }; + + target.Measure(new Size(200, 200)); + target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); + + Assert.Equal(new Size(200, 100), target.DesiredSize); + var scaleTransform = target.Child.RenderTransform as ScaleTransform; + + Assert.NotNull(scaleTransform); + Assert.Equal(2.0, scaleTransform.ScaleX); + Assert.Equal(2.0, scaleTransform.ScaleY); + } + + [Fact] + public void Viewbox_Stretch_None_Child() + { + var target = new Viewbox() { Stretch = Stretch.None, Child = new Rectangle() { Width = 100, Height = 50 } }; + + target.Measure(new Size(200, 200)); + target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); + + Assert.Equal(new Size(100, 50), target.DesiredSize); + var scaleTransform = target.Child.RenderTransform as ScaleTransform; + + Assert.NotNull(scaleTransform); + Assert.Equal(1.0, scaleTransform.ScaleX); + Assert.Equal(1.0, scaleTransform.ScaleY); + } + + [Fact] + public void Viewbox_Stretch_Fill_Child() + { + var target = new Viewbox() { Stretch = Stretch.Fill, Child = new Rectangle() { Width = 100, Height = 50 } }; + + target.Measure(new Size(200, 200)); + target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); + + Assert.Equal(new Size(200, 200), target.DesiredSize); + var scaleTransform = target.Child.RenderTransform as ScaleTransform; + + Assert.NotNull(scaleTransform); + Assert.Equal(2.0, scaleTransform.ScaleX); + Assert.Equal(4.0, scaleTransform.ScaleY); + } + + [Fact] + public void Viewbox_Stretch_UniformToFill_Child() + { + var target = new Viewbox() { Stretch = Stretch.UniformToFill, Child = new Rectangle() { Width = 100, Height = 50 } }; + + target.Measure(new Size(200, 200)); + target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); + + Assert.Equal(new Size(200, 200), target.DesiredSize); + var scaleTransform = target.Child.RenderTransform as ScaleTransform; + + Assert.NotNull(scaleTransform); + Assert.Equal(4.0, scaleTransform.ScaleX); + Assert.Equal(4.0, scaleTransform.ScaleY); + } + + [Fact] + public void Viewbox_Stretch_Uniform_Child_With_Unrestricted_Width() + { + var target = new Viewbox() { Child = new Rectangle() { Width = 100, Height = 50 } }; + + target.Measure(new Size(double.PositiveInfinity, 200)); + target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); + + Assert.Equal(new Size(400, 200), target.DesiredSize); + var scaleTransform = target.Child.RenderTransform as ScaleTransform; + + Assert.NotNull(scaleTransform); + Assert.Equal(4.0, scaleTransform.ScaleX); + Assert.Equal(4.0, scaleTransform.ScaleY); + } + + [Fact] + public void Viewbox_Stretch_Uniform_Child_With_Unrestricted_Height() + { + var target = new Viewbox() { Child = new Rectangle() { Width = 100, Height = 50 } }; + + target.Measure(new Size(200, double.PositiveInfinity)); + target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); + + Assert.Equal(new Size(200, 100), target.DesiredSize); + var scaleTransform = target.Child.RenderTransform as ScaleTransform; + + Assert.NotNull(scaleTransform); + Assert.Equal(2.0, scaleTransform.ScaleX); + Assert.Equal(2.0, scaleTransform.ScaleY); + } + } +} From e92ff04bba30d2895ae3d85bab821daa5550dfd1 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sat, 10 Nov 2018 22:17:56 +0200 Subject: [PATCH 006/147] set defaut cliptobounds to true for layouttransform and viewbox --- src/Avalonia.Controls/LayoutTransformControl.cs | 2 ++ src/Avalonia.Controls/ViewBox.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index 73d5e61e03..3616e7b574 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -22,6 +22,8 @@ namespace Avalonia.Controls static LayoutTransformControl() { + ClipToBoundsProperty.OverrideDefaultValue(true); + LayoutTransformProperty.Changed .AddClassHandler(x => x.OnLayoutTransformChanged); diff --git a/src/Avalonia.Controls/ViewBox.cs b/src/Avalonia.Controls/ViewBox.cs index 7492e32937..94d72f3e6c 100644 --- a/src/Avalonia.Controls/ViewBox.cs +++ b/src/Avalonia.Controls/ViewBox.cs @@ -34,6 +34,7 @@ namespace Avalonia.Controls static Viewbox() { AffectsMeasure(StretchProperty); + ClipToBoundsProperty.OverrideDefaultValue(true); } protected override Size MeasureOverride(Size availableSize) From bf833a5926cafb32a888b38aa68490c2106c5337 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sat, 10 Nov 2018 22:19:35 +0200 Subject: [PATCH 007/147] rename Viewbox file --- src/Avalonia.Controls/{ViewBox.cs => Viewbox.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Avalonia.Controls/{ViewBox.cs => Viewbox.cs} (100%) diff --git a/src/Avalonia.Controls/ViewBox.cs b/src/Avalonia.Controls/Viewbox.cs similarity index 100% rename from src/Avalonia.Controls/ViewBox.cs rename to src/Avalonia.Controls/Viewbox.cs From 4a5ac3f4bc47d318ebe2c95f1e188686245e3f43 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sun, 11 Nov 2018 01:55:27 +0200 Subject: [PATCH 008/147] fix tests --- src/Avalonia.Controls/LayoutTransformControl.cs | 2 +- src/Avalonia.Controls/Viewbox.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index 3616e7b574..950d4f34da 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -22,7 +22,7 @@ namespace Avalonia.Controls static LayoutTransformControl() { - ClipToBoundsProperty.OverrideDefaultValue(true); + ClipToBoundsProperty.OverrideDefaultValue(true); LayoutTransformProperty.Changed .AddClassHandler(x => x.OnLayoutTransformChanged); diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index 94d72f3e6c..db753f4ab4 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -33,8 +33,8 @@ namespace Avalonia.Controls static Viewbox() { - AffectsMeasure(StretchProperty); ClipToBoundsProperty.OverrideDefaultValue(true); + AffectsMeasure(StretchProperty); } protected override Size MeasureOverride(Size availableSize) From dbfb9288d15d0f2023019ae1a7ae82b24f61a185 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sun, 11 Nov 2018 18:03:14 +0200 Subject: [PATCH 009/147] add viewbox page to control catalog --- samples/ControlCatalog/MainView.xaml | 1 + samples/ControlCatalog/Pages/ViewboxPage.xaml | 65 +++++++++++++++++++ .../ControlCatalog/Pages/ViewboxPage.xaml.cs | 18 +++++ 3 files changed, 84 insertions(+) create mode 100644 samples/ControlCatalog/Pages/ViewboxPage.xaml create mode 100644 samples/ControlCatalog/Pages/ViewboxPage.xaml.cs diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index ec3bf799b4..f2630b3c18 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -20,6 +20,7 @@ + diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml b/samples/ControlCatalog/Pages/ViewboxPage.xaml new file mode 100644 index 0000000000..89a82c4791 --- /dev/null +++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml @@ -0,0 +1,65 @@ + + + + F1 M 16.6309,18.6563C 17.1309, + 8.15625 29.8809,14.1563 29.8809, + 14.1563C 30.8809,11.1563 34.1308, + 11.4063 34.1308,11.4063C 33.5,12 + 34.6309,13.1563 34.6309,13.1563C + 32.1309,13.1562 31.1309,14.9062 + 31.1309,14.9062C 41.1309,23.9062 + 32.6309,27.9063 32.6309,27.9062C + 24.6309,24.9063 21.1309,22.1562 + 16.6309,18.6563 Z M 16.6309,19.9063C + 21.6309,24.1563 25.1309,26.1562 + 31.6309,28.6562C 31.6309,28.6562 + 26.3809,39.1562 18.3809,36.1563C + 18.3809,36.1563 18,38 16.3809,36.9063C + 15,36 16.3809,34.9063 16.3809,34.9063C + 16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z + + + + + + Viewbox + A control used to scale single child. + + + None + Fill + Uniform + UniformToFill + + + Hello World! + + + Hello World! + + + Hello World! + + + Hello World! + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs new file mode 100644 index 0000000000..1b5f4bc7f4 --- /dev/null +++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs @@ -0,0 +1,18 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class ViewboxPage : UserControl + { + public ViewboxPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} From 8477d085d098eb877d12d542a80253f89b370653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Wed, 14 Nov 2018 00:17:33 +0100 Subject: [PATCH 010/147] Make grid sizes Auto in scrollbar template --- src/Avalonia.Themes.Default/ScrollBar.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml index ae40929573..2f0d48db27 100644 --- a/src/Avalonia.Themes.Default/ScrollBar.xaml +++ b/src/Avalonia.Themes.Default/ScrollBar.xaml @@ -3,7 +3,7 @@ - + - + Date: Wed, 14 Nov 2018 00:26:13 +0100 Subject: [PATCH 011/147] Added ScrollBar size resources --- src/Avalonia.Themes.Default/Accents/BaseDark.xaml | 3 +++ src/Avalonia.Themes.Default/Accents/BaseLight.xaml | 3 +++ src/Avalonia.Themes.Default/ScrollBar.xaml | 8 ++++---- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml index 95b351b148..2a20892869 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml @@ -52,5 +52,8 @@ 10 12 16 + + 10 + 10 diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index 4d1c4b1ab0..e94653a3d6 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -52,5 +52,8 @@ 10 12 16 + + 10 + 10 diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml index 2f0d48db27..e22a62ee7f 100644 --- a/src/Avalonia.Themes.Default/ScrollBar.xaml +++ b/src/Avalonia.Themes.Default/ScrollBar.xaml @@ -54,7 +54,7 @@ - + diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index d0a746f87d..463a8a8db6 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -6,10 +6,11 @@ %(Filename) - + Designer - - + + + @@ -24,4 +25,5 @@ + diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index b2f6497caa..37014255cc 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -1,6 +1,8 @@  @@ -30,4 +32,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index ec3bf799b4..db66812e77 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -1,6 +1,7 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + x:Class="ControlCatalog.MainView"> diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 7029273a84..ab65fdebaf 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -1,6 +1,8 @@  + Icon="/Assets/test_icon.ico?assembly=ControlCatalog" + xmlns:local="clr-namespace:ControlCatalog" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + x:Class="ControlCatalog.MainWindow"> - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml index 28bdc7ac71..0ca3567970 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml @@ -1,4 +1,6 @@ - + AutoCompleteBox A control into which the user can input text diff --git a/samples/ControlCatalog/Pages/BorderPage.xaml b/samples/ControlCatalog/Pages/BorderPage.xaml index 09f591d59d..c30056d5e5 100644 --- a/samples/ControlCatalog/Pages/BorderPage.xaml +++ b/samples/ControlCatalog/Pages/BorderPage.xaml @@ -1,4 +1,6 @@ - + Border A control which decorates a child with a border and background diff --git a/samples/ControlCatalog/Pages/ButtonPage.xaml b/samples/ControlCatalog/Pages/ButtonPage.xaml index a4690e32e1..39d89590c2 100644 --- a/samples/ControlCatalog/Pages/ButtonPage.xaml +++ b/samples/ControlCatalog/Pages/ButtonPage.xaml @@ -1,5 +1,6 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + x:Class="ControlCatalog.Pages.ButtonPage"> Button A button control diff --git a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml index fba15f6e77..c3f9f65dd9 100644 --- a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml +++ b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml @@ -1,5 +1,6 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + x:Class="ControlCatalog.Pages.ButtonSpinnerPage"> ButtonSpinner diff --git a/samples/ControlCatalog/Pages/CalendarPage.xaml b/samples/ControlCatalog/Pages/CalendarPage.xaml index c47fd766fb..dbd0dba37b 100644 --- a/samples/ControlCatalog/Pages/CalendarPage.xaml +++ b/samples/ControlCatalog/Pages/CalendarPage.xaml @@ -1,5 +1,6 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + x:Class="ControlCatalog.Pages.CalendarPage"> Calendar A calendar control for selecting dates diff --git a/samples/ControlCatalog/Pages/CanvasPage.xaml b/samples/ControlCatalog/Pages/CanvasPage.xaml index d6c138a4f7..d154e717a4 100644 --- a/samples/ControlCatalog/Pages/CanvasPage.xaml +++ b/samples/ControlCatalog/Pages/CanvasPage.xaml @@ -1,4 +1,6 @@ - + Canvas A panel which lays out its children by explicit coordinates diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml b/samples/ControlCatalog/Pages/CarouselPage.xaml index cf9b13c00c..b08ff565e3 100644 --- a/samples/ControlCatalog/Pages/CarouselPage.xaml +++ b/samples/ControlCatalog/Pages/CarouselPage.xaml @@ -1,4 +1,6 @@ - + Carousel An items control that displays its items as pages that fill the control. @@ -11,9 +13,9 @@ - - - + + + diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml b/samples/ControlCatalog/Pages/DragAndDropPage.xaml index 1f3cd3ff71..9bfcd90149 100644 --- a/samples/ControlCatalog/Pages/DragAndDropPage.xaml +++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml @@ -1,4 +1,6 @@ - + Drag+Drop Example of Drag+Drop capabilities diff --git a/samples/ControlCatalog/Pages/DropDownPage.xaml b/samples/ControlCatalog/Pages/DropDownPage.xaml index 5e2a3102e7..864d2be49c 100644 --- a/samples/ControlCatalog/Pages/DropDownPage.xaml +++ b/samples/ControlCatalog/Pages/DropDownPage.xaml @@ -1,4 +1,6 @@ - + DropDown A drop-down list. diff --git a/samples/ControlCatalog/Pages/ExpanderPage.xaml b/samples/ControlCatalog/Pages/ExpanderPage.xaml index 91440929f5..605eff4fce 100644 --- a/samples/ControlCatalog/Pages/ExpanderPage.xaml +++ b/samples/ControlCatalog/Pages/ExpanderPage.xaml @@ -1,4 +1,6 @@ - + Expander Expands to show nested content diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml b/samples/ControlCatalog/Pages/ImagePage.xaml index 78fbf90192..b44fac27cb 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml +++ b/samples/ControlCatalog/Pages/ImagePage.xaml @@ -1,4 +1,6 @@ - + Image Displays an image @@ -9,28 +11,28 @@ Spacing="16"> No Stretch - Fill - Uniform - UniformToFill - @@ -40,4 +42,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml b/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml index f7e1c08cac..446dfd7ce1 100644 --- a/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml +++ b/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml @@ -1,5 +1,6 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + x:Class="ControlCatalog.Pages.LayoutTransformControlPage"> Rotation @@ -23,4 +24,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index 3dd8be91c2..4783c8cfb8 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -1,4 +1,6 @@ - + ListBox Hosts a collection of ListBoxItem. diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml index c5aa35312c..e1a5cf2c5a 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml +++ b/samples/ControlCatalog/Pages/MenuPage.xaml @@ -1,4 +1,6 @@ - + Menu A window menu @@ -19,7 +21,7 @@ - + diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml index 2bb6214b58..273deefab4 100644 --- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml +++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml @@ -1,5 +1,6 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + x:Class="ControlCatalog.Pages.NumericUpDownPage"> Numeric up-down control Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel. diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml b/samples/ControlCatalog/Pages/ProgressBarPage.xaml index a40aa0ea9a..39bbf391bb 100644 --- a/samples/ControlCatalog/Pages/ProgressBarPage.xaml +++ b/samples/ControlCatalog/Pages/ProgressBarPage.xaml @@ -1,4 +1,6 @@ - + ProgressBar A progress bar control @@ -21,4 +23,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/RadioButtonPage.xaml b/samples/ControlCatalog/Pages/RadioButtonPage.xaml index 9525f6187e..bf31c40e2a 100644 --- a/samples/ControlCatalog/Pages/RadioButtonPage.xaml +++ b/samples/ControlCatalog/Pages/RadioButtonPage.xaml @@ -1,5 +1,6 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + x:Class="ControlCatalog.Pages.RadioButtonPage"> RadioButton Allows the selection of a single option of many @@ -37,4 +38,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml b/samples/ControlCatalog/Pages/SliderPage.xaml index 6db71b5fcc..58f7b881fe 100644 --- a/samples/ControlCatalog/Pages/SliderPage.xaml +++ b/samples/ControlCatalog/Pages/SliderPage.xaml @@ -1,4 +1,6 @@ - + Slider A control that lets the user select from a range of values by moving a Thumb control along a Track. @@ -18,4 +20,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index 5f94e15cec..5540f725b5 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -1,4 +1,6 @@ - + TextBox A control into which the user can input text @@ -33,11 +35,20 @@ Text="Multiline TextBox with no TextWrapping. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." /> + resm fonts + + + res fonts + + + + + - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml b/samples/ControlCatalog/Pages/ToolTipPage.xaml index ad832b9b82..cbe1e3059c 100644 --- a/samples/ControlCatalog/Pages/ToolTipPage.xaml +++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml @@ -1,4 +1,6 @@ - + ToolTip @@ -38,4 +40,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml b/samples/ControlCatalog/Pages/TreeViewPage.xaml index 1ab49dbb30..f8f3cd5848 100644 --- a/samples/ControlCatalog/Pages/TreeViewPage.xaml +++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml @@ -1,4 +1,6 @@ - + TreeView Displays a hierachical tree of data. @@ -16,4 +18,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index cc3c31d13a..1078409c17 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -1,5 +1,6 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + x:Class="ControlCatalog.SideBar"> diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index e94653a3d6..c40305151e 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -53,7 +53,6 @@ 12 16 - 10 - 10 + 10 diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml index 982af43587..2cb8ce2d24 100644 --- a/src/Avalonia.Themes.Default/ScrollBar.xaml +++ b/src/Avalonia.Themes.Default/ScrollBar.xaml @@ -53,7 +53,7 @@ - + + + From b8b7de237d50f44c4925c4787e12215cf4084f41 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 01:06:05 +0200 Subject: [PATCH 043/147] fit dropdown popup in screen --- src/Avalonia.Themes.Default/DropDown.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 451f1c2f23..4684e1c872 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -35,6 +35,7 @@ MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}" MaxHeight="{TemplateBinding MaxDropDownHeight}" PlacementTarget="{TemplateBinding}" + ObeyScreenEdges="True" StaysOpen="False"> From 59a286ba103ff673779e8d58ea95151d3d95d696 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 01:10:29 +0200 Subject: [PATCH 044/147] let dropdown support virtualization if needed --- src/Avalonia.Controls/DropDown.cs | 25 ++++++++++++++++++++++- src/Avalonia.Themes.Default/DropDown.xaml | 5 ++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index a30cc231b7..d17089c127 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -4,8 +4,10 @@ using System; using System.Linq; using Avalonia.Controls.Generators; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; +using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Media; @@ -13,12 +15,17 @@ using Avalonia.VisualTree; namespace Avalonia.Controls { - /// /// A drop-down list control. /// public class DropDown : SelectingItemsControl { + /// + /// The default value for the property. + /// + private static readonly FuncTemplate DefaultPanel = + new FuncTemplate(() => new VirtualizingStackPanel()); + /// /// Defines the property. /// @@ -40,6 +47,12 @@ namespace Avalonia.Controls public static readonly DirectProperty SelectionBoxItemProperty = AvaloniaProperty.RegisterDirect(nameof(SelectionBoxItem), o => o.SelectionBoxItem); + /// + /// Defines the property. + /// + public static readonly StyledProperty VirtualizationModeProperty = + ItemsPresenter.VirtualizationModeProperty.AddOwner(); + private bool _isDropDownOpen; private Popup _popup; private object _selectionBoxItem; @@ -49,6 +62,7 @@ namespace Avalonia.Controls /// static DropDown() { + ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); FocusableProperty.OverrideDefaultValue(true); SelectedItemProperty.Changed.AddClassHandler(x => x.SelectedItemChanged); KeyDownEvent.AddClassHandler(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel); @@ -81,6 +95,15 @@ namespace Avalonia.Controls set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } } + /// + /// Gets or sets the virtualization mode for the items. + /// + public ItemVirtualizationMode VirtualizationMode + { + get { return GetValue(VirtualizationModeProperty); } + set { SetValue(VirtualizationModeProperty, value); } + } + /// protected override IItemContainerGenerator CreateItemContainerGenerator() { diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 4684e1c872..9603bba107 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -42,8 +42,11 @@ + MemberSelector="{TemplateBinding MemberSelector}" + VirtualizationMode="{TemplateBinding VirtualizationMode}" + /> From c4ab6648338e14489c27e5aef49f2f863e34c3c7 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 13:34:29 +0200 Subject: [PATCH 045/147] Use Adorner Focus styling for focused DropDownItem --- src/Avalonia.Controls/DropDown.cs | 10 ++++++---- src/Avalonia.Themes.Default/DropDown.xaml | 20 ++++++++++--------- src/Avalonia.Themes.Default/DropDownItem.xaml | 5 ----- src/Avalonia.Themes.Default/FocusAdorner.xaml | 5 +++-- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index d17089c127..2197047936 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -165,10 +165,10 @@ namespace Avalonia.Controls else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 && (e.Key == Key.Up || e.Key == Key.Down)) { - var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => c.Focusable); + var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c)); if (firstChild != null) { - firstChild.Focus(); + FocusManager.Instance?.Focus(firstChild, NavigationMethod.Directional); e.Handled = true; } } @@ -223,7 +223,7 @@ namespace Avalonia.Controls private void PopupClosed(object sender, EventArgs e) { - if (Focusable) + if (CanFocus(this)) { Focus(); } @@ -246,13 +246,15 @@ namespace Avalonia.Controls if (IsDropDownOpen && selectedIndex != -1) { var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); - if (container != null && container.Focusable) + if (container != null && CanFocus(container)) { container.Focus(); } } } + private bool CanFocus(IControl control) => control.Focusable && control.IsEnabledCore && control.IsVisible; + private void UpdateSelectionBoxItem(object item) { var contentControl = item as IContentControl; diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 9603bba107..ad2be275d6 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -39,15 +39,17 @@ StaysOpen="False"> - - + + - + + @@ -58,4 +60,4 @@ - \ No newline at end of file + diff --git a/src/Avalonia.Themes.Default/DropDownItem.xaml b/src/Avalonia.Themes.Default/DropDownItem.xaml index a15e770bf5..f542a34d71 100644 --- a/src/Avalonia.Themes.Default/DropDownItem.xaml +++ b/src/Avalonia.Themes.Default/DropDownItem.xaml @@ -4,7 +4,6 @@ - - - diff --git a/src/Avalonia.Themes.Default/FocusAdorner.xaml b/src/Avalonia.Themes.Default/FocusAdorner.xaml index 573c43dc8d..2d5e369573 100644 --- a/src/Avalonia.Themes.Default/FocusAdorner.xaml +++ b/src/Avalonia.Themes.Default/FocusAdorner.xaml @@ -3,7 +3,8 @@ + StrokeDashArray="1,2" + Margin="1"/> - \ No newline at end of file + From 850cbfdbf0b251f92c7b8a37fd8996fd437ee1fd Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 13:42:57 +0200 Subject: [PATCH 046/147] Support out of the box Member Selector in DropDown --- src/Avalonia.Controls/DropDown.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index 2197047936..f7cb0aa0ef 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -284,7 +284,8 @@ namespace Avalonia.Controls } else { - SelectionBoxItem = item; + var selector = MemberSelector; + SelectionBoxItem = selector != null ? selector.Select(item) : item; } } From daadd9c674ce27da2a4496bbd62223b865a4dc68 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 14:02:57 +0200 Subject: [PATCH 047/147] support member selector when recycling Dropdown/listboxitems --- .../Primitives/SelectingItemsControl.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index ccbdc71b1d..c40ddc37ad 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -431,9 +431,12 @@ namespace Avalonia.Controls.Primitives { if (i.ContainerControl != null && i.Item != null) { - MarkContainerSelected( - i.ContainerControl, - SelectedItems.Contains(i.Item)); + var ms = MemberSelector; + bool selected = ms == null ? + SelectedItems.Contains(i.Item) : + SelectedItems.OfType().Any(v => Equals(ms.Select(v), i.Item)); + + MarkContainerSelected(i.ContainerControl, selected); } } } From cd7c5eb762aa493a1d73a0d1474ee8e0d40f3477 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 14:15:57 +0200 Subject: [PATCH 048/147] Ensure selected DropDownItem is visible when dropdown is open --- src/Avalonia.Controls/DropDown.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index f7cb0aa0ef..93b33e0589 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -246,6 +246,13 @@ namespace Avalonia.Controls if (IsDropDownOpen && selectedIndex != -1) { var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); + + if(container == null && SelectedItems.Count > 0) + { + ScrollIntoView(SelectedItems[0]); + container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); + } + if (container != null && CanFocus(container)) { container.Focus(); From dd571f131d753a86dde6492d342d366f1f4293b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Tue, 20 Nov 2018 17:27:37 +0100 Subject: [PATCH 049/147] Added theme switching support for ControlCatalog --- samples/ControlCatalog/MainView.xaml | 67 ++++++++++++++----------- samples/ControlCatalog/MainView.xaml.cs | 17 +++++++ 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 7dd8098300..7c2ae441d0 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -1,32 +1,41 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + Background="{DynamicResource ThemeBackgroundBrush}" + Foreground="{DynamicResource ThemeForegroundBrush}" + FontSize="{DynamicResource FontSizeNormal}"> + + + Light + Dark + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 0be5d25a09..a498b17bdd 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -2,6 +2,7 @@ using System.Collections; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.Markup.Xaml.Styling; using Avalonia.Platform; using ControlCatalog.Pages; @@ -27,6 +28,22 @@ namespace ControlCatalog }); } + var light = AvaloniaXamlLoader.Parse(@""); + var dark = AvaloniaXamlLoader.Parse(@""); + var themes = this.Find("Themes"); + themes.SelectionChanged += (sender, e) => + { + switch (themes.SelectedIndex) + { + case 0: + Styles[0] = light; + break; + case 1: + Styles[0] = dark; + break; + } + }; + Styles.Add(light); } private void InitializeComponent() From f55388a2476f1aca75e20c6428c5a247820a48ff Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 22 Nov 2018 11:32:34 +0100 Subject: [PATCH 050/147] Fixes render demo with new tab control --- samples/RenderDemo/MainWindow.xaml | 5 +- samples/RenderDemo/SideBar.xaml | 113 ++++++++++++++++------------- 2 files changed, 64 insertions(+), 54 deletions(-) diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml index fc5b1ce94d..41164c7780 100644 --- a/samples/RenderDemo/MainWindow.xaml +++ b/samples/RenderDemo/MainWindow.xaml @@ -24,9 +24,6 @@ - - - @@ -38,4 +35,4 @@ - \ No newline at end of file + diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml index 26da2cc556..624c1a7b28 100644 --- a/samples/RenderDemo/SideBar.xaml +++ b/samples/RenderDemo/SideBar.xaml @@ -1,53 +1,66 @@ - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > + - - - - - + + + + + From e44eaf9a24a97f4fce0614aea6517366aa22ed0c Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 22 Nov 2018 12:07:47 +0100 Subject: [PATCH 051/147] Improved default control template for TabControl --- .../ControlCatalog/Pages/TabControlPage.xaml | 35 +++---------------- samples/ControlCatalog/SideBar.xaml | 3 +- .../Generators/TabItemContainerGenerator.cs | 2 ++ src/Avalonia.Controls/TabItem.cs | 8 +---- src/Avalonia.Themes.Default/TabControl.xaml | 19 ++++++---- src/Avalonia.Themes.Default/TabItem.xaml | 8 ++++- 6 files changed, 28 insertions(+), 47 deletions(-) diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml index 5b10e7d790..430ac28347 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml @@ -26,43 +26,19 @@ - - - - - + This is the first page in the TabControl. - - - - - + This is the second page in the TabControl. - - - - - + You should not see this. @@ -82,10 +58,7 @@ + Text="{Binding Header}"> diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 3ec8e43b07..ae9ab7f6a6 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -17,8 +17,7 @@ VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" Background="{TemplateBinding Background}"> IsSelectedProperty = ListBoxItem.IsSelectedProperty.AddOwner(); - private TabControl _parentTabControl; - /// /// Initializes static members of the class. /// @@ -56,11 +54,7 @@ namespace Avalonia.Controls set { SetValue(IsSelectedProperty, value); } } - internal TabControl ParentTabControl - { - get => _parentTabControl; - set => _parentTabControl = value; - } + internal TabControl ParentTabControl { get; set; } private void UpdateHeader(AvaloniaPropertyChangedEventArgs obj) { diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml index 28de70ebb6..a492698feb 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -1,10 +1,5 @@ - + + + + diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml index 6d7cdea1fd..fcdb76524e 100644 --- a/src/Avalonia.Themes.Default/TabItem.xaml +++ b/src/Avalonia.Themes.Default/TabItem.xaml @@ -3,9 +3,11 @@ + + - @@ -36,4 +39,7 @@ + From 1a16e423789c46b959c7675d70999e31de77af6b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 23 Nov 2018 15:28:30 +0000 Subject: [PATCH 052/147] prototype, allowing to draw in non-client area. without breaking win32 frame functionality. --- samples/ControlCatalog/MainWindow.xaml | 8 +- src/Avalonia.Visuals/Rect.cs | 7 ++ .../Interop/UnmanagedMethods.cs | 12 +++ src/Windows/Avalonia.Win32/WindowImpl.cs | 96 +++++++++++++++++++ 4 files changed, 120 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 7029273a84..4be11f13b3 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -1,6 +1,8 @@  - - \ No newline at end of file + xmlns:local="clr-namespace:ControlCatalog" Background="Transparent"> + + + + diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 63d34b474b..37c320ab39 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -120,6 +120,8 @@ namespace Avalonia /// public Size Size => new Size(_width, _height); + public double Left => _x; + /// /// Gets the right position of the rectangle. /// @@ -130,6 +132,11 @@ namespace Avalonia /// public double Bottom => _y + _height; + /// + /// Gets the top position of the rectangle. + /// + public double Top => _y; + /// /// Gets the top left point of the rectangle. /// diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 9e8ff20e44..98eb6f1e30 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -907,6 +907,18 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")] public static extern bool SetWindowText(IntPtr hwnd, string lpString); + [StructLayout(LayoutKind.Sequential)] + public struct MARGINS + { + public int leftWidth; + public int rightWidth; + public int topHeight; + public int bottomHeight; + } + + [DllImport("dwmapi.dll")] + public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); + public enum ClassLongIndex : int { GCL_HCURSOR = -12, diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 18f0696cd8..395ea43999 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -455,6 +455,54 @@ namespace Avalonia.Win32 IntPtr.Zero); } + + protected int BorderTop { get; private set; } + protected int BorderLeft { get; private set; } + protected int BorderRight { get; private set; } + protected int BorderBottom { get; private set; } + + private HitTestValues NCHitText(uint m, IntPtr LParam, IntPtr wParam) + { + int lParam = (int)LParam; + Point pt = new Point(lParam & 0xffff, lParam >> 16); + + var rc = new Rect(Position, ClientSize); + + int row = 1; + int col = 1; + bool onResizeBorder = false; + + // Determine if we are on the top or bottom border + if (pt.Y >= rc.Top && pt.Y < rc.Top + BorderTop) + { + onResizeBorder = pt.Y < (rc.Top + BorderBottom); + row = 0; + } + else if (pt.Y < rc.Bottom && pt.Y > rc.Bottom - BorderBottom) + { + row = 2; + } + + // Determine if we are on the left border or the right border + if (pt.X >= rc.Left && pt.X < rc.Left + BorderLeft) + { + col = 0; + } + else if (pt.X < rc.Right && pt.X >= rc.Right - BorderRight) + { + col = 2; + } + + HitTestValues[,] hitTests = new HitTestValues[,] + { + {HitTestValues.HTTOPLEFT, onResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT}, + {HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT}, + {HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT} + }; + + return hitTests[row, col]; + } + [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { @@ -465,6 +513,8 @@ namespace Avalonia.Win32 RawInputEventArgs e = null; + bool callDWP = false; + WindowsMouseDevice.Instance.CurrentWindow = this; switch ((UnmanagedMethods.WindowsMessage)msg) @@ -484,8 +534,54 @@ namespace Avalonia.Win32 break; } + MARGINS margins = new MARGINS(); + + margins.leftWidth = Math.Abs(BorderLeft); + margins.rightWidth = Math.Abs(BorderRight); + margins.bottomHeight = Math.Abs(BorderBottom); + margins.topHeight = Math.Abs(BorderTop); + + // int hr = UnmanagedMethods.DwmExtendFrameIntoClientArea(hWnd, ref margins); + return IntPtr.Zero; + case WindowsMessage.WM_NCCALCSIZE: + return IntPtr.Zero; + //break; + + case WindowsMessage.WM_CREATE: + RECT rcClient; + UnmanagedMethods.GetWindowRect(hWnd, out rcClient); + + + RECT rc = new RECT(); + uint style = UnmanagedMethods.GetWindowLong(hWnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + uint styleEx = UnmanagedMethods.GetWindowLong(hWnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); + UnmanagedMethods.AdjustWindowRectEx(ref rc, style, false, styleEx); + + BorderTop = Math.Abs(rc.top); + BorderLeft = Math.Abs(0); + BorderRight = Math.Abs(0); + BorderBottom = Math.Abs(0); + break; + + case WindowsMessage.WM_NCHITTEST: + var ht = NCHitText(msg, lParam, wParam); + + + callDWP = (ht == HitTestValues.HTNOWHERE); + + if (callDWP) + { + + } + else + { + return new IntPtr((int)ht); + } + break; + + case UnmanagedMethods.WindowsMessage.WM_CLOSE: bool? preventClosing = Closing?.Invoke(); if (preventClosing == true) From 6f540cf2609931004b92080fb1208600584972a0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 23 Nov 2018 15:43:27 +0000 Subject: [PATCH 053/147] fix removing decorations --- samples/ControlCatalog/MainWindow.xaml | 6 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 131 +++-------------------- 2 files changed, 19 insertions(+), 118 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 4be11f13b3..e722206fd0 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -2,7 +2,5 @@ Title="Avalonia Control Gallery" Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog" xmlns:local="clr-namespace:ControlCatalog" Background="Transparent"> - - - - + + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 395ea43999..17b72955bc 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -269,11 +269,6 @@ namespace Avalonia.Win32 style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; - if (!value) - { - style ^= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; - } - UnmanagedMethods.RECT windowRect; UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); @@ -282,32 +277,29 @@ namespace Avalonia.Win32 var oldThickness = BorderThickness; UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); + + var thickness = BorderThickness; + + _decorated = value; - if (value) - { - var thickness = BorderThickness; + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else + if(!value) { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + RECT rc = new RECT(); + uint style1 = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + uint styleEx1 = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); + UnmanagedMethods.AdjustWindowRectEx(ref rc, style1, false, styleEx1); } UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, (int)newRect.Height, UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); - _decorated = value; - if(_decorated) { if (_resizable) @@ -455,54 +447,6 @@ namespace Avalonia.Win32 IntPtr.Zero); } - - protected int BorderTop { get; private set; } - protected int BorderLeft { get; private set; } - protected int BorderRight { get; private set; } - protected int BorderBottom { get; private set; } - - private HitTestValues NCHitText(uint m, IntPtr LParam, IntPtr wParam) - { - int lParam = (int)LParam; - Point pt = new Point(lParam & 0xffff, lParam >> 16); - - var rc = new Rect(Position, ClientSize); - - int row = 1; - int col = 1; - bool onResizeBorder = false; - - // Determine if we are on the top or bottom border - if (pt.Y >= rc.Top && pt.Y < rc.Top + BorderTop) - { - onResizeBorder = pt.Y < (rc.Top + BorderBottom); - row = 0; - } - else if (pt.Y < rc.Bottom && pt.Y > rc.Bottom - BorderBottom) - { - row = 2; - } - - // Determine if we are on the left border or the right border - if (pt.X >= rc.Left && pt.X < rc.Left + BorderLeft) - { - col = 0; - } - else if (pt.X < rc.Right && pt.X >= rc.Right - BorderRight) - { - col = 2; - } - - HitTestValues[,] hitTests = new HitTestValues[,] - { - {HitTestValues.HTTOPLEFT, onResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT}, - {HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT}, - {HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT} - }; - - return hitTests[row, col]; - } - [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { @@ -513,8 +457,6 @@ namespace Avalonia.Win32 RawInputEventArgs e = null; - bool callDWP = false; - WindowsMouseDevice.Instance.CurrentWindow = this; switch ((UnmanagedMethods.WindowsMessage)msg) @@ -534,53 +476,14 @@ namespace Avalonia.Win32 break; } - MARGINS margins = new MARGINS(); - - margins.leftWidth = Math.Abs(BorderLeft); - margins.rightWidth = Math.Abs(BorderRight); - margins.bottomHeight = Math.Abs(BorderBottom); - margins.topHeight = Math.Abs(BorderTop); - - // int hr = UnmanagedMethods.DwmExtendFrameIntoClientArea(hWnd, ref margins); - return IntPtr.Zero; case WindowsMessage.WM_NCCALCSIZE: - return IntPtr.Zero; - //break; - - case WindowsMessage.WM_CREATE: - RECT rcClient; - UnmanagedMethods.GetWindowRect(hWnd, out rcClient); - - - RECT rc = new RECT(); - uint style = UnmanagedMethods.GetWindowLong(hWnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - uint styleEx = UnmanagedMethods.GetWindowLong(hWnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); - UnmanagedMethods.AdjustWindowRectEx(ref rc, style, false, styleEx); - - BorderTop = Math.Abs(rc.top); - BorderLeft = Math.Abs(0); - BorderRight = Math.Abs(0); - BorderBottom = Math.Abs(0); - break; - - case WindowsMessage.WM_NCHITTEST: - var ht = NCHitText(msg, lParam, wParam); - - - callDWP = (ht == HitTestValues.HTNOWHERE); - - if (callDWP) + if (!_decorated) { - - } - else - { - return new IntPtr((int)ht); + return IntPtr.Zero; } - break; - + break; case UnmanagedMethods.WindowsMessage.WM_CLOSE: bool? preventClosing = Closing?.Invoke(); From 7483b1cb4cfd4b30017453ba3b3e5ab6eedfa035 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 23 Nov 2018 15:44:47 +0000 Subject: [PATCH 054/147] revert changes. --- samples/ControlCatalog/MainWindow.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index e722206fd0..4aa16aabfa 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -1,6 +1,6 @@  - + xmlns:local="clr-namespace:ControlCatalog"> + From 6a76c00f3ee5bfb09269cf879d956edaa92d2a0f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 23 Nov 2018 15:46:29 +0000 Subject: [PATCH 055/147] remove unused changes. --- src/Avalonia.Visuals/Rect.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 37c320ab39..86597627b6 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -120,8 +120,6 @@ namespace Avalonia /// public Size Size => new Size(_width, _height); - public double Left => _x; - /// /// Gets the right position of the rectangle. /// @@ -131,12 +129,7 @@ namespace Avalonia /// Gets the bottom position of the rectangle. /// public double Bottom => _y + _height; - - /// - /// Gets the top position of the rectangle. - /// - public double Top => _y; - + /// /// Gets the top left point of the rectangle. /// From 607703427da97fcdcdbed7cbde93a7aa5666e30e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 23 Nov 2018 15:47:13 +0000 Subject: [PATCH 056/147] whitespace. --- src/Avalonia.Visuals/Rect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 86597627b6..63d34b474b 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -129,7 +129,7 @@ namespace Avalonia /// Gets the bottom position of the rectangle. /// public double Bottom => _y + _height; - + /// /// Gets the top left point of the rectangle. /// From 9f02b7f7c12449fafa8b97fd2ab0b3fb6c41815c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 23 Nov 2018 15:48:13 +0000 Subject: [PATCH 057/147] remove unused method. --- .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 98eb6f1e30..9e8ff20e44 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -907,18 +907,6 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")] public static extern bool SetWindowText(IntPtr hwnd, string lpString); - [StructLayout(LayoutKind.Sequential)] - public struct MARGINS - { - public int leftWidth; - public int rightWidth; - public int topHeight; - public int bottomHeight; - } - - [DllImport("dwmapi.dll")] - public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); - public enum ClassLongIndex : int { GCL_HCURSOR = -12, From f1c2c72400ee18d10f3e796ae788fce9a06efb9a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 24 Nov 2018 15:50:47 +0100 Subject: [PATCH 058/147] Added failing tests for #1614. --- .../Media/VisualBrushTests.cs | 123 ++++++++++++++++++ tests/Avalonia.RenderTests/TestBase.cs | 8 +- ...sualBrush_Checkerboard_96_Dpi.expected.png | Bin 0 -> 765 bytes ...sualBrush_Checkerboard_96_Dpi.expected.png | Bin 0 -> 765 bytes 4 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Checkerboard_96_Dpi.expected.png create mode 100644 tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Checkerboard_96_Dpi.expected.png diff --git a/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs b/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs index 6f90dfd19e..aee83d5e0b 100644 --- a/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs @@ -449,5 +449,128 @@ namespace Avalonia.Direct2D1.RenderTests.Media await RenderToFile(target); CompareImages(); } + + [Fact] + public async Task VisualBrush_Checkerboard_96_Dpi() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = new VisualBrush + { + DestinationRect = new RelativeRect(0, 0, 16, 16, RelativeUnit.Absolute), + TileMode = TileMode.Tile, + Visual = new Canvas + { + Width = 16, + Height= 16, + Background = Brushes.Red, + Children = + { + new Rectangle + { + Width = 8, + Height = 8, + Fill = Brushes.Green, + }, + new Rectangle + { + Width = 8, + Height = 8, + Fill = Brushes.Green, + [Canvas.LeftProperty] = 8, + [Canvas.TopProperty] = 8, + }, + } + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task VisualBrush_Checkerboard_144_Dpi() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = new VisualBrush + { + DestinationRect = new RelativeRect(0, 0, 16, 16, RelativeUnit.Absolute), + TileMode = TileMode.Tile, + Visual = new Canvas + { + Width = 16, + Height = 16, + Background = Brushes.Red, + Children = + { + new Rectangle + { + Width = 8, + Height = 8, + Fill = Brushes.Green, + }, + new Rectangle + { + Width = 8, + Height = 8, + Fill = Brushes.Green, + [Canvas.LeftProperty] = 8, + [Canvas.TopProperty] = 8, + }, + } + } + } + }; + + await RenderToFile(target, dpi: 144); + CompareImages(); + } + + [Fact] + public async Task VisualBrush_Checkerboard_192_Dpi() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = new VisualBrush + { + DestinationRect = new RelativeRect(0, 0, 16, 16, RelativeUnit.Absolute), + TileMode = TileMode.Tile, + Visual = new Canvas + { + Width = 16, + Height = 16, + Background = Brushes.Red, + Children = + { + new Rectangle + { + Width = 8, + Height = 8, + Fill = Brushes.Green, + }, + new Rectangle + { + Width = 8, + Height = 8, + Fill = Brushes.Green, + [Canvas.LeftProperty] = 8, + [Canvas.TopProperty] = 8, + }, + } + } + } + }; + + await RenderToFile(target, dpi: 192); + CompareImages(); + } } } diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 2892615e48..4c8abd85f7 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -63,7 +63,7 @@ namespace Avalonia.Direct2D1.RenderTests get; } - protected async Task RenderToFile(Control target, [CallerMemberName] string testName = "") + protected async Task RenderToFile(Control target, [CallerMemberName] string testName = "", double dpi = 96) { if (!Directory.Exists(OutputPath)) { @@ -75,9 +75,9 @@ namespace Avalonia.Direct2D1.RenderTests var factory = AvaloniaLocator.Current.GetService(); var pixelSize = new PixelSize((int)target.Width, (int)target.Height); var size = new Size(target.Width, target.Height); - var dpi = new Vector(96, 96); + var dpiVector = new Vector(dpi, dpi); - using (RenderTargetBitmap bitmap = new RenderTargetBitmap(pixelSize, dpi)) + using (RenderTargetBitmap bitmap = new RenderTargetBitmap(pixelSize, dpiVector)) { target.Measure(size); target.Arrange(new Rect(size)); @@ -85,7 +85,7 @@ namespace Avalonia.Direct2D1.RenderTests bitmap.Save(immediatePath); } - using (var rtb = factory.CreateRenderTargetBitmap(pixelSize, dpi)) + using (var rtb = factory.CreateRenderTargetBitmap(pixelSize, dpiVector)) using (var renderer = new DeferredRenderer(target, rtb)) { target.Measure(size); diff --git a/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Checkerboard_96_Dpi.expected.png b/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Checkerboard_96_Dpi.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..0f88a0d4683ac02fd277cca06d5a7257f1d45ecc GIT binary patch literal 765 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VM%xNb!1@J z*w6hZkrl}2EbxddW?ba4!+h@5toZc^y};8W>2}7 zt7(*N%9e8KEaTIA*PmCd`%rVQzxns{`?vSYvwwSk{9ye1{rBb{;AdfSR1iQA@1DQ< z-~7Y+=kLOV|8+8dzVGEmm1ki(SS|2-%f50)1rC-b2PC3`KWYB8?|K|8jSd3H#Cyj# zmw(-3ZB!88U_v4u*ml%L-QSIDH`o<>*f;vWt`RX)@0(D~Zglt{cf@Y( t`&d-7fmYpXzOnr4sN?A$@)o<+<$wOs@-f%`u@M6hc)I$ztaD0e0suUW=+yuK literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Checkerboard_96_Dpi.expected.png b/tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Checkerboard_96_Dpi.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..0f88a0d4683ac02fd277cca06d5a7257f1d45ecc GIT binary patch literal 765 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VM%xNb!1@J z*w6hZkrl}2EbxddW?ba4!+h@5toZc^y};8W>2}7 zt7(*N%9e8KEaTIA*PmCd`%rVQzxns{`?vSYvwwSk{9ye1{rBb{;AdfSR1iQA@1DQ< z-~7Y+=kLOV|8+8dzVGEmm1ki(SS|2-%f50)1rC-b2PC3`KWYB8?|K|8jSd3H#Cyj# zmw(-3ZB!88U_v4u*ml%L-QSIDH`o<>*f;vWt`RX)@0(D~Zglt{cf@Y( t`&d-7fmYpXzOnr4sN?A$@)o<+<$wOs@-f%`u@M6hc)I$ztaD0e0suUW=+yuK literal 0 HcmV?d00001 From eecd3555f97c9a19307c2edec2994ae2483ae6d0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 24 Nov 2018 16:38:41 +0100 Subject: [PATCH 059/147] Fix DPI in ImageBrushImpl. And add expected output for tests. D2D now passes, skia still renders incorrectly. --- .../Rendering/Utilities/TileBrushCalculator.cs | 14 +++++--------- .../Avalonia.Direct2D1/Media/ImageBrushImpl.cs | 5 +++-- .../VisualBrush_Checkerboard_144_Dpi.expected.png | Bin 0 -> 752 bytes .../VisualBrush_Checkerboard_192_Dpi.expected.png | Bin 0 -> 732 bytes .../VisualBrush_Checkerboard_144_Dpi.expected.png | Bin 0 -> 752 bytes .../VisualBrush_Checkerboard_192_Dpi.expected.png | Bin 0 -> 732 bytes 6 files changed, 8 insertions(+), 11 deletions(-) create mode 100644 tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Checkerboard_144_Dpi.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Checkerboard_192_Dpi.expected.png create mode 100644 tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Checkerboard_144_Dpi.expected.png create mode 100644 tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Checkerboard_192_Dpi.expected.png diff --git a/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs b/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs index 97af33c3e2..52a8fb5ab7 100644 --- a/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs +++ b/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs @@ -9,7 +9,6 @@ namespace Avalonia.Rendering.Utilities { private readonly Size _imageSize; private readonly Rect _drawRect; - private readonly Vector _dpi; public bool IsValid { get; } @@ -19,7 +18,7 @@ namespace Avalonia.Rendering.Utilities /// The brush to be rendered. /// The size of the content of the tile brush. /// The size of the control to which the brush is being rendered. - public TileBrushCalculator(ITileBrush brush, Size contentSize, Size targetSize, Vector dpi) + public TileBrushCalculator(ITileBrush brush, Size contentSize, Size targetSize) : this( brush.TileMode, brush.Stretch, @@ -28,8 +27,7 @@ namespace Avalonia.Rendering.Utilities brush.SourceRect, brush.DestinationRect, contentSize, - targetSize, - dpi) + targetSize) { } @@ -52,14 +50,12 @@ namespace Avalonia.Rendering.Utilities RelativeRect sourceRect, RelativeRect destinationRect, Size contentSize, - Size targetSize, - Vector dpi) + Size targetSize) { _imageSize = contentSize; - _dpi = dpi; - SourceRect = sourceRect.ToPixels(_imageSize) * (_dpi / 96); - DestinationRect = destinationRect.ToPixels(targetSize) * (dpi / 96); + SourceRect = sourceRect.ToPixels(_imageSize); + DestinationRect = destinationRect.ToPixels(targetSize); var scale = stretch.CalculateScaling(DestinationRect.Size, SourceRect.Size); var translate = CalculateTranslate(alignmentX, alignmentY, SourceRect, DestinationRect, scale); diff --git a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs index 6c2a88af17..2ca2a77fb8 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs @@ -20,8 +20,9 @@ namespace Avalonia.Direct2D1.Media BitmapImpl bitmap, Size targetSize) { - var calc = new TileBrushCalculator(brush, bitmap.PixelSize.ToSize(96), targetSize, new Vector(target.DotsPerInch.Width, target.DotsPerInch.Height)); - + var dpi = new Vector(target.DotsPerInch.Width, target.DotsPerInch.Height); + var calc = new TileBrushCalculator(brush, bitmap.PixelSize.ToSize(dpi), targetSize); + if (!calc.NeedsIntermediate) { _bitmap = bitmap.GetDirect2DBitmap(target); diff --git a/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Checkerboard_144_Dpi.expected.png b/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Checkerboard_144_Dpi.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..7e674b849ae4f5763b302d07b57aff122e612679 GIT binary patch literal 752 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VM%xNb!1@J z*w6hZkrl}2EbxddW?&Fg1z|?dAe9dc3{0(_E{-7;ac}P^<}o`8xE{PY=Z^V(0q5tX zVO*Z8yKWr#^=4(K>GjIFzikTk-;1w%S@om(_u0DNum7(9uKuCXL4kv%2@7$kbbk8( zqo3~2OWXfVM%xNb!1@J z*w6hZkrl}2EbxddW?+zQ2Vutkryo-p7?=t@T^vIy;@;j>%sb*B;CisoZ=ZR2N8&fe z?M_jlDry(q{d>cT=bqnr{=nZ#ySQC#|MUOVM%xNb!1@J z*w6hZkrl}2EbxddW?&Fg1z|?dAe9dc3{0(_E{-7;ac}P^<}o`8xE{PY=Z^V(0q5tX zVO*Z8yKWr#^=4(K>GjIFzikTk-;1w%S@om(_u0DNum7(9uKuCXL4kv%2@7$kbbk8( zqo3~2OWXfVM%xNb!1@J z*w6hZkrl}2EbxddW?+zQ2Vutkryo-p7?=t@T^vIy;@;j>%sb*B;CisoZ=ZR2N8&fe z?M_jlDry(q{d>cT=bqnr{=nZ#ySQC#|MUO Date: Sat, 24 Nov 2018 20:26:40 +0100 Subject: [PATCH 060/147] Fix DPI scaling for Skia tile brushes. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 48 +++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 1bfd7fd90d..076d69ae4a 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -225,10 +225,7 @@ namespace Avalonia.Skia /// public IRenderTargetBitmapImpl CreateLayer(Size size) { - var normalizedDpi = new Vector(_dpi.X / SkiaPlatform.DefaultDpi.X, _dpi.Y / SkiaPlatform.DefaultDpi.Y); - var pixelSize = size * normalizedDpi; - - return CreateRenderTarget((int) pixelSize.Width, (int) pixelSize.Height, _dpi); + return CreateRenderTarget(size); } /// @@ -387,26 +384,27 @@ namespace Avalonia.Skia /// Target size. /// Tile brush to use. /// Tile brush image. - /// The bitmap interpolation mode. private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage) { - var calc = new TileBrushCalculator(tileBrush, - new Size(tileBrushImage.PixelSize.Width, tileBrushImage.PixelSize.Height), targetSize); - - var intermediate = CreateRenderTarget( - (int)calc.IntermediateSize.Width, - (int)calc.IntermediateSize.Height, _dpi); + var calc = new TileBrushCalculator(tileBrush, tileBrushImage.PixelSize.ToSize(_dpi), targetSize); + var intermediate = CreateRenderTarget(calc.IntermediateSize); paintWrapper.AddDisposable(intermediate); using (var context = intermediate.CreateDrawingContext(null)) { - var rect = new Rect(0, 0, tileBrushImage.PixelSize.Width, tileBrushImage.PixelSize.Height); + var sourceRect = new Rect(tileBrushImage.PixelSize.ToSize(96)); + var targetRect = new Rect(tileBrushImage.PixelSize.ToSize(_dpi)); context.Clear(Colors.Transparent); context.PushClip(calc.IntermediateClip); context.Transform = calc.IntermediateTransform; - context.DrawImage(RefCountable.CreateUnownedNotClonable(tileBrushImage), 1, rect, rect, tileBrush.BitmapInterpolationMode); + context.DrawImage( + RefCountable.CreateUnownedNotClonable(tileBrushImage), + 1, + sourceRect, + targetRect, + tileBrush.BitmapInterpolationMode); context.PopClip(); } @@ -433,7 +431,14 @@ namespace Avalonia.Skia var image = intermediate.SnapshotImage(); paintWrapper.AddDisposable(image); - using (var shader = image.ToShader(tileX, tileY, tileTransform)) + var paintTransform = default(SKMatrix); + + SKMatrix.Concat( + ref paintTransform, + tileTransform, + SKMatrix.MakeScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y))); + + using (var shader = image.ToShader(tileX, tileY, paintTransform)) { paintWrapper.Paint.Shader = shader; } @@ -457,7 +462,7 @@ namespace Avalonia.Skia if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1) { - var intermediate = CreateRenderTarget((int)intermediateSize.Width, (int)intermediateSize.Height, _dpi); + var intermediate = CreateRenderTarget(intermediateSize); using (var ctx = intermediate.CreateDrawingContext(visualBrushRenderer)) { @@ -609,18 +614,17 @@ namespace Avalonia.Skia /// /// Create new render target compatible with this drawing context. /// - /// Width. - /// Height. - /// Drawing dpi. + /// The size of the render target in DIPs. /// Pixel format. /// - private SurfaceRenderTarget CreateRenderTarget(int width, int height, Vector dpi, PixelFormat? format = null) + private SurfaceRenderTarget CreateRenderTarget(Size size, PixelFormat? format = null) { + var pixelSize = PixelSize.FromSize(size, _dpi); var createInfo = new SurfaceRenderTarget.CreateInfo { - Width = width, - Height = height, - Dpi = dpi, + Width = pixelSize.Width, + Height = pixelSize.Height, + Dpi = _dpi, Format = format, DisableTextLcdRendering = !_canTextUseLcdRendering, GrContext = _grContext From 53638845a64510c2afb505432b5e97e2029de6f1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 24 Nov 2018 20:54:57 +0000 Subject: [PATCH 061/147] Add more visualbrush tests. --- .../Media/VisualBrushTests.cs | 153 ++++++++++++++++++ .../VisualBrush_Grip_144_Dpi.expected.png | Bin 0 -> 177 bytes .../VisualBrush_Grip_192_Dpi.expected.png | Bin 0 -> 161 bytes .../VisualBrush_Grip_96_Dpi.expected.png | Bin 0 -> 152 bytes .../VisualBrush_Grip_144_Dpi.expected.png | Bin 0 -> 177 bytes .../VisualBrush_Grip_192_Dpi.expected.png | Bin 0 -> 161 bytes .../VisualBrush_Grip_96_Dpi.expected.png | Bin 0 -> 152 bytes 7 files changed, 153 insertions(+) create mode 100644 tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_192_Dpi.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_96_Dpi.expected.png create mode 100644 tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png create mode 100644 tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_192_Dpi.expected.png create mode 100644 tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_96_Dpi.expected.png diff --git a/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs b/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs index aee83d5e0b..7c53f4516a 100644 --- a/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs @@ -450,6 +450,159 @@ namespace Avalonia.Direct2D1.RenderTests.Media CompareImages(); } + [Fact] + public async Task VisualBrush_Grip_96_Dpi() + { + var target = new Border + { + Width = 100, + Height = 10, + Background = new VisualBrush + { + SourceRect = new RelativeRect(0, 0, 4, 5, RelativeUnit.Absolute), + DestinationRect = new RelativeRect(0, 0, 4, 5, RelativeUnit.Absolute), + TileMode = TileMode.Tile, + Stretch = Stretch.UniformToFill, + Visual = new Canvas + { + Width = 4, + Height = 5, + Background = Brushes.WhiteSmoke, + Children = + { + new Rectangle + { + Width = 1, + Height = 1, + Fill = Brushes.Red, + [Canvas.LeftProperty] = 2, + }, + new Rectangle + { + Width = 1, + Height = 1, + Fill = Brushes.Red, + [Canvas.TopProperty] = 2, + }, + new Rectangle + { + Width = 1, + Height = 1, + Fill = Brushes.Red, + [Canvas.LeftProperty] = 2, + [Canvas.TopProperty] = 4, + } + } + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task VisualBrush_Grip_144_Dpi() + { + var target = new Border + { + Width = 100, + Height = 7.5, + Background = new VisualBrush + { + SourceRect = new RelativeRect(0, 0, 4, 5, RelativeUnit.Absolute), + DestinationRect = new RelativeRect(0, 0, 4, 5, RelativeUnit.Absolute), + TileMode = TileMode.Tile, + Stretch = Stretch.UniformToFill, + Visual = new Canvas + { + Width = 4, + Height = 5, + Background = Brushes.WhiteSmoke, + Children = + { + new Rectangle + { + Width = 1, + Height = 1, + Fill = Brushes.Red, + [Canvas.LeftProperty] = 2, + }, + new Rectangle + { + Width = 1, + Height = 1, + Fill = Brushes.Red, + [Canvas.TopProperty] = 2, + }, + new Rectangle + { + Width = 1, + Height = 1, + Fill = Brushes.Red, + [Canvas.LeftProperty] = 2, + [Canvas.TopProperty] = 4, + } + } + } + } + }; + + await RenderToFile(target, dpi: 144); + CompareImages(); + } + + [Fact] + public async Task VisualBrush_Grip_192_Dpi() + { + var target = new Border + { + Width = 100, + Height = 10, + Background = new VisualBrush + { + SourceRect = new RelativeRect(0, 0, 4, 5, RelativeUnit.Absolute), + DestinationRect = new RelativeRect(0, 0, 4, 5, RelativeUnit.Absolute), + TileMode = TileMode.Tile, + Stretch = Stretch.UniformToFill, + Visual = new Canvas + { + Width = 4, + Height = 5, + Background = Brushes.WhiteSmoke, + Children = + { + new Rectangle + { + Width = 1, + Height = 1, + Fill = Brushes.Red, + [Canvas.LeftProperty] = 2, + }, + new Rectangle + { + Width = 1, + Height = 1, + Fill = Brushes.Red, + [Canvas.TopProperty] = 2, + }, + new Rectangle + { + Width = 1, + Height = 1, + Fill = Brushes.Red, + [Canvas.LeftProperty] = 2, + [Canvas.TopProperty] = 4, + } + } + } + } + }; + + await RenderToFile(target, dpi: 192); + CompareImages(); + } + [Fact] public async Task VisualBrush_Checkerboard_96_Dpi() { diff --git a/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png b/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..d530c7251963cfb32eaab6c1021bf90f819793df GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^DL~B5!3HG%HN98>q*#ibJVQ8upoSx*1IW+tba4#v z=zM$Ckgq|3r}bg_jVA(gSop5#=@dQIQ;Sd+z98%QV*ZAjGr5XNzie2)s9d^s=J>AU zXFbcLlucOn7|TDN-SNVG?foKmO-Y7zQ+D;=t)9_ee%z&U+s4?Nxv9Qkb)cOLp00i_>zopr09)=snE(I) literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_192_Dpi.expected.png b/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_192_Dpi.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a209dc94cc9b721670d928d6f842bf71b209a74e GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^DL~A{!3HFqWPaBHDVAa<&kznEsNqQI0P-U|T^vI^ zI^SM3mLjB4k&yLu-XeAu&N!n0T7J7B+lDR8`m$J?Oc$JxvKPlagx8~YT y0pms8FV7yR=Wi6kjKY<(f0&D>XFZ$0zd;;mG=rzBpUXO@geCyONHeAY literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png b/tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..d530c7251963cfb32eaab6c1021bf90f819793df GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^DL~B5!3HG%HN98>q*#ibJVQ8upoSx*1IW+tba4#v z=zM$Ckgq|3r}bg_jVA(gSop5#=@dQIQ;Sd+z98%QV*ZAjGr5XNzie2)s9d^s=J>AU zXFbcLlucOn7|TDN-SNVG?foKmO-Y7zQ+D;=t)9_ee%z&U+s4?Nxv9Qkb)cOLp00i_>zopr09)=snE(I) literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_192_Dpi.expected.png b/tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_192_Dpi.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a209dc94cc9b721670d928d6f842bf71b209a74e GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^DL~A{!3HFqWPaBHDVAa<&kznEsNqQI0P-U|T^vI^ zI^SM3mLjB4k&yLu-XeAu&N!n0T7J7B+lDR8`m$J?Oc$JxvKPlagx8~YT y0pms8FV7yR=Wi6kjKY<(f0&D>XFZ$0zd;;mG=rzBpUXO@geCyONHeAY literal 0 HcmV?d00001 From f82ee356bd6ff47cc521a6a06250c58268283281 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 24 Nov 2018 22:58:47 +0100 Subject: [PATCH 062/147] Added debugging util to save D2D render targets. --- .../Imaging/D2DRenderTargetBitmapImpl.cs | 19 +++++++++++++++++++ .../Avalonia.Direct2D1/Utils/DebugUtils.cs | 13 +++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/Windows/Avalonia.Direct2D1/Utils/DebugUtils.cs diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 72367ac50f..fbe7f840d7 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -1,8 +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. +using System.IO; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Utilities; using SharpDX; using SharpDX.Direct2D1; using D2DBitmap = SharpDX.Direct2D1.Bitmap; @@ -49,5 +51,22 @@ namespace Avalonia.Direct2D1.Media.Imaging { return new OptionalDispose(_renderTarget.Bitmap, false); } + + public override void Save(Stream stream) + { + using (var wic = new WicRenderTargetBitmapImpl(PixelSize, Dpi)) + { + using (var dc = wic.CreateDrawingContext(null)) + { + dc.DrawImage( + RefCountable.CreateUnownedNotClonable(this), + 1, + new Rect(PixelSize.ToSize(Dpi.X)), + new Rect(PixelSize.ToSize(Dpi.X))); + } + + wic.Save(stream); + } + } } } diff --git a/src/Windows/Avalonia.Direct2D1/Utils/DebugUtils.cs b/src/Windows/Avalonia.Direct2D1/Utils/DebugUtils.cs new file mode 100644 index 0000000000..9ce8f15cf4 --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/Utils/DebugUtils.cs @@ -0,0 +1,13 @@ +using Avalonia.Direct2D1.Media.Imaging; + +namespace Avalonia.Direct2D1.Utils +{ + internal static class DebugUtils + { + public static void Save(SharpDX.Direct2D1.BitmapRenderTarget bitmap, string filename) + { + var rtb = new D2DRenderTargetBitmapImpl(bitmap); + rtb.Save(filename); + } + } +} From 41f201bec87d156525fd115ca35eb965520883e4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 24 Nov 2018 23:46:15 +0100 Subject: [PATCH 063/147] Fix grip visualbrush tests. --- src/Avalonia.Visuals/Media/PixelSize.cs | 4 ++-- .../Rendering/Utilities/TileBrushCalculator.cs | 4 ++-- .../Media/DrawingContextImpl.cs | 8 +++++++- .../Avalonia.Direct2D1/Media/ImageBrushImpl.cs | 3 ++- .../VisualBrush_Grip_144_Dpi.expected.png | Bin 177 -> 271 bytes 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PixelSize.cs b/src/Avalonia.Visuals/Media/PixelSize.cs index 3d0ae0b351..6785b51716 100644 --- a/src/Avalonia.Visuals/Media/PixelSize.cs +++ b/src/Avalonia.Visuals/Media/PixelSize.cs @@ -159,8 +159,8 @@ namespace Avalonia /// The dots per inch. /// The device-independent size. public static PixelSize FromSize(Size size, Vector dpi) => new PixelSize( - (int)(size.Width * (dpi.X / 96)), - (int)(size.Height * (dpi.Y / 96))); + (int)Math.Ceiling(size.Width * (dpi.X / 96)), + (int)Math.Ceiling(size.Height * (dpi.Y / 96))); /// /// Returns the string representation of the size. diff --git a/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs b/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs index 52a8fb5ab7..27949fcf55 100644 --- a/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs +++ b/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs @@ -116,8 +116,8 @@ namespace Avalonia.Rendering.Utilities return true; if (SourceRect.Size.AspectRatio == _imageSize.AspectRatio) return false; - if ((int)SourceRect.Width != _imageSize.Width || - (int)SourceRect.Height != _imageSize.Height) + if (SourceRect.Width != _imageSize.Width || + SourceRect.Height != _imageSize.Height) return true; return false; } diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index efe26ba01a..aaca1a3b00 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -434,10 +434,16 @@ namespace Avalonia.Direct2D1.Media if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1) { + // We need to ensure the size we're requesting is an integer pixel size, otherwise + // D2D alters the DPI of the render target, which messes stuff up. PixelSize.FromSize + // will do the rounding for us. + var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); + var pixelSize = PixelSize.FromSize(intermediateSize, dpi); + using (var intermediate = new BitmapRenderTarget( _deviceContext, CompatibleRenderTargetOptions.None, - intermediateSize.ToSharpDX())) + pixelSize.ToSize(dpi).ToSharpDX())) { using (var ctx = new RenderTarget(intermediate).CreateDrawingContext(_visualBrushRenderer)) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs index 2ca2a77fb8..19d320b783 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs @@ -100,7 +100,8 @@ namespace Avalonia.Direct2D1.Media using (var context = new RenderTarget(result).CreateDrawingContext(null)) { - var rect = new Rect(0, 0, bitmap.PixelSize.Width, bitmap.PixelSize.Height); + var dpi = new Vector(target.DotsPerInch.Width, target.DotsPerInch.Height); + var rect = new Rect(bitmap.PixelSize.ToSize(dpi)); context.Clear(Colors.Transparent); context.PushClip(calc.IntermediateClip); diff --git a/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png b/tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png index d530c7251963cfb32eaab6c1021bf90f819793df..1900ade5e258af8cf1d0206d405afc63e8e6e4ef 100644 GIT binary patch delta 243 zcmV^*_tiKf>^*b8_dZ(#2& zEQf_HjF=f0Pmn4fLdctRL*Tuqy9l7)F#KnAdAH*@PPpSej%=<2)pdjz^$-C!*U!We zX5T?s2IP5^5cLoVx1VL<3pv_0ETkzvzWbiLx~wP1bv9+m_>6jpw43j`5>*whdM4R6 tHg#FI!#qzY3Lak0ai^vU3+lq|Z~`{Olf9W;h1>uD002ovPDHLkV1h;GVTb?# delta 149 zcmV;G0BZk_01D- zDH#B+l)8`Idzg=R$3%Iyv0d41WldIh)MpvnmHh^SQ1O!Ua}Ek800000NkvXXu0mjf D4W~W7 From 3279cbd4e86e6376396f87ae99e84e2eacb2f1e7 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 25 Nov 2018 12:52:11 +0300 Subject: [PATCH 064/147] Fixed some review comments --- src/Avalonia.Base/Avalonia.Base.csproj | 4 +- .../Utilities/AvaloniaResourcesIndex.cs | 5 +- .../Avalonia.Build.Tasks.csproj | 3 +- .../BuildEngineErrorCode.cs | 9 ++ src/Avalonia.Build.Tasks/Extensions.cs | 14 +-- .../GenerateAvaloniaResourcesTask.cs | 11 +-- src/Shared/PlatformSupport/AssetLoader.cs | 90 +++++++++---------- 7 files changed, 73 insertions(+), 63 deletions(-) create mode 100644 src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index abc4ed80d8..e10f13d628 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -7,7 +7,9 @@ false - + + false + diff --git a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs index d9dc36001c..c7d1e47148 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs @@ -9,10 +9,7 @@ using System.Runtime.Serialization.Json; namespace Avalonia.Utilities { - #if !BUILDTASK - public - #endif - static class AvaloniaResourcesIndexReaderWriter + public static class AvaloniaResourcesIndexReaderWriter { private const int LastKnownVersion = 1; public static List Read(Stream stream) diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index acaaa34462..6b697a9028 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -4,6 +4,7 @@ netstandard2.0 tools $(DefineConstants);BUILDTASK + false @@ -15,6 +16,4 @@ - - diff --git a/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs b/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs new file mode 100644 index 0000000000..3b8a390881 --- /dev/null +++ b/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs @@ -0,0 +1,9 @@ +namespace Avalonia.Build.Tasks +{ + public enum BuildEngineErrorCode + { + InvalidXAML = 1, + DuplicateXClass = 2, + LegacyResmScheme = 3, + } +} diff --git a/src/Avalonia.Build.Tasks/Extensions.cs b/src/Avalonia.Build.Tasks/Extensions.cs index d86c5a5b48..faecee0f37 100644 --- a/src/Avalonia.Build.Tasks/Extensions.cs +++ b/src/Avalonia.Build.Tasks/Extensions.cs @@ -4,16 +4,18 @@ namespace Avalonia.Build.Tasks { public static class Extensions { - public static void LogError(this IBuildEngine engine, string file, string message) + static string FormatErrorCode(BuildEngineErrorCode code) => $"AVLN:{(int)code:0000}"; + + public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message) { - engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "0000", file ?? "", 0, 0, 0, 0, message, "", - "Avalonia")); + engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message, + "", "Avalonia")); } - public static void LogWarning(this IBuildEngine engine, string file, string message) + public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message) { - engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "0000", file ?? "", 0, 0, 0, 0, message, "", - "Avalonia")); + engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message, + "", "Avalonia")); } } } diff --git a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs index 57cde0bf12..98ebb3e7d1 100644 --- a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs +++ b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs @@ -67,7 +67,7 @@ namespace Avalonia.Build.Tasks List BuildResourceSources() => Resources.Select(r => new Source(r.ItemSpec, Root)).ToList(); - void Pack(Stream output, List sources) + private void Pack(Stream output, List sources) { var offsets = new Dictionary(); var coffset = 0; @@ -94,7 +94,7 @@ namespace Avalonia.Build.Tasks } } - bool PreProcessXamlFiles(List sources) + private bool PreProcessXamlFiles(List sources) { var typeToXamlIndex = new Dictionary(); @@ -109,7 +109,7 @@ namespace Avalonia.Build.Tasks } catch(Exception e) { - BuildEngine.LogError(s.SystemPath, "File doesn't contain valid XAML: " + e); + BuildEngine.LogError(BuildEngineErrorCode.InvalidXAML, s.SystemPath, "File doesn't contain valid XAML: " + e); return false; } @@ -118,7 +118,7 @@ namespace Avalonia.Build.Tasks if (typeToXamlIndex.ContainsKey(info.XClass)) { - BuildEngine.LogError(s.SystemPath, + BuildEngine.LogError(BuildEngineErrorCode.DuplicateXClass, s.SystemPath, $"Duplicate x:Class directive, {info.XClass} is already used in {typeToXamlIndex[info.XClass]}"); return false; } @@ -140,7 +140,8 @@ namespace Avalonia.Build.Tasks public bool Execute() { foreach(var r in EmbeddedResources.Where(r=>r.ItemSpec.EndsWith(".xaml")||r.ItemSpec.EndsWith(".paml"))) - BuildEngine.LogWarning(r.ItemSpec, "XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work"); + BuildEngine.LogWarning(BuildEngineErrorCode.LegacyResmScheme, r.ItemSpec, + "XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work"); var resources = BuildResourceSources(); if (!PreProcessXamlFiles(resources)) diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index cea607040d..22d702967b 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -296,60 +296,60 @@ namespace Avalonia.Shared.PlatformSupport } class SlicedStream : Stream - { - private readonly Stream _baseStream; - private readonly int _from; - - public SlicedStream(Stream baseStream, int from, int length) - { - Length = length; - _baseStream = baseStream; - _from = from; - _baseStream.Position = from; - } - public override void Flush() - { - } + { + private readonly Stream _baseStream; + private readonly int _from; - public override int Read(byte[] buffer, int offset, int count) - { - return _baseStream.Read(buffer, offset, (int)Math.Min(count, Length - Position)); - } + public SlicedStream(Stream baseStream, int from, int length) + { + Length = length; + _baseStream = baseStream; + _from = from; + _baseStream.Position = from; + } + public override void Flush() + { + } - public override long Seek(long offset, SeekOrigin origin) - { - if (origin == SeekOrigin.Begin) - Position = offset; - if (origin == SeekOrigin.End) - Position = _from + Length + offset; - if (origin == SeekOrigin.Current) - Position = Position + offset; - return Position; - } + public override int Read(byte[] buffer, int offset, int count) + { + return _baseStream.Read(buffer, offset, (int)Math.Min(count, Length - Position)); + } - public override void SetLength(long value) => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) + { + if (origin == SeekOrigin.Begin) + Position = offset; + if (origin == SeekOrigin.End) + Position = _from + Length + offset; + if (origin == SeekOrigin.Current) + Position = Position + offset; + return Position; + } - public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); - public override bool CanRead => true; - public override bool CanSeek => _baseStream.CanRead; - public override bool CanWrite => false; - public override long Length { get; } - public override long Position - { - get => _baseStream.Position - _from; - set => _baseStream.Position = value + _from; - } + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - protected override void Dispose(bool disposing) - { - if (disposing) - _baseStream.Dispose(); - } + public override bool CanRead => true; + public override bool CanSeek => _baseStream.CanRead; + public override bool CanWrite => false; + public override long Length { get; } + public override long Position + { + get => _baseStream.Position - _from; + set => _baseStream.Position = value + _from; + } - public override void Close() => _baseStream.Close(); + protected override void Dispose(bool disposing) + { + if (disposing) + _baseStream.Dispose(); } + public override void Close() => _baseStream.Close(); + } + private class AssemblyDescriptor { public AssemblyDescriptor(Assembly assembly) From 6347a610c7ec866f85d23c29ff09f0e9c2f408a6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 25 Nov 2018 13:55:44 +0300 Subject: [PATCH 065/147] Switched back to #if since ReferenceOutputAssembly doesn't work properly for classic project format --- src/Avalonia.Base/Avalonia.Base.csproj | 4 +--- .../Utilities/AvaloniaResourcesIndex.cs | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index e10f13d628..abc4ed80d8 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -7,9 +7,7 @@ false - - false - + diff --git a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs index c7d1e47148..22e5c952bf 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs @@ -9,7 +9,10 @@ using System.Runtime.Serialization.Json; namespace Avalonia.Utilities { - public static class AvaloniaResourcesIndexReaderWriter + #if !BUILDTASK + public + #endif + static class AvaloniaResourcesIndexReaderWriter { private const int LastKnownVersion = 1; public static List Read(Stream stream) @@ -34,14 +37,20 @@ namespace Avalonia.Utilities } [DataContract] - public class AvaloniaResourcesIndex +#if !BUILDTASK + public +#endif + class AvaloniaResourcesIndex { [DataMember] public List Entries { get; set; } = new List(); } [DataContract] - public class AvaloniaResourcesIndexEntry +#if !BUILDTASK + public +#endif + class AvaloniaResourcesIndexEntry { [DataMember] public string Path { get; set; } From 0b8286ff0e35513b0c9d999efaa2c09ca1e82684 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 25 Nov 2018 14:59:07 +0300 Subject: [PATCH 066/147] Merge-caused invalid xaml --- samples/ControlCatalog/MainView.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 5763d35aeb..0f7f2e80a8 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -1,7 +1,7 @@ + x:Class="ControlCatalog.MainView" Background="{DynamicResource ThemeBackgroundBrush}" Foreground="{DynamicResource ThemeForegroundBrush}" FontSize="{DynamicResource FontSizeNormal}"> From de2a8728de98112291bd4f8c3c33dd0d27f8870d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 25 Nov 2018 15:24:33 +0300 Subject: [PATCH 067/147] Fixed TabControl and Viewbox pages --- samples/ControlCatalog/Pages/TabControlPage.xaml | 9 ++++++--- samples/ControlCatalog/Pages/TabControlPage.xaml.cs | 4 ++-- samples/ControlCatalog/Pages/ViewboxPage.xaml | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml index 430ac28347..01ddc0ddca 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml @@ -1,4 +1,7 @@ - + This is the first page in the TabControl. - + This is the second page in the TabControl. - + diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs index 808d90a49c..a38a3ab4cb 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs @@ -26,13 +26,13 @@ namespace ControlCatalog.Pages { Header = "Arch", Text = "This is the first templated tab page.", - Image = LoadBitmap("resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg?assembly=ControlCatalog"), + Image = LoadBitmap("avares://ControlCatalog/Assets/delicate-arch-896885_640.jpg"), }, new TabItemViewModel { Header = "Leaf", Text = "This is the second templated tab page.", - Image = LoadBitmap("resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg?assembly=ControlCatalog"), + Image = LoadBitmap("avares://ControlCatalog/Assets/maple-leaf-888807_640.jpg"), }, new TabItemViewModel { diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml b/samples/ControlCatalog/Pages/ViewboxPage.xaml index 89a82c4791..e78cf2bc22 100644 --- a/samples/ControlCatalog/Pages/ViewboxPage.xaml +++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml @@ -1,5 +1,6 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + x:Class="ControlCatalog.Pages.ViewboxPage"> F1 M 16.6309,18.6563C 17.1309, From 1e91484c689562b257cc1d75828bc95fd79583f6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 26 Nov 2018 20:58:23 +0300 Subject: [PATCH 068/147] Added to AvaloniaBuildTasks.targets --- packages/Avalonia/AvaloniaBuildTasks.targets | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 3ddd60e2af..10f971cc4c 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -36,4 +36,8 @@ Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration)"/> + + + + From f260ec1df43dd227a23ed69fa473833f54100d63 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 26 Nov 2018 23:40:00 +0100 Subject: [PATCH 069/147] Support relative paths for embedded fonts --- samples/ControlCatalog/Pages/TextBoxPage.xaml | 8 +- src/Avalonia.Visuals/Media/FontFamily.cs | 16 +++- .../Media/Fonts/FontFamilyKey.cs | 66 +++++--------- .../Media/Fonts/FontFamilyLoader.cs | 88 +++++++++++++++---- .../Avalonia.Markup.Xaml.csproj | 1 + .../AvaloniaTypeConverters.cs | 9 +- .../Converters/FontFamilyTypeConverter.cs | 28 ++++++ .../Media/Fonts/FontFamilyKeyTests.cs | 4 +- 8 files changed, 148 insertions(+), 72 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index 2c292b1478..67b60568e5 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -44,10 +44,10 @@ res fonts - - - - + + + + diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index 16dba573fd..d32db4ef28 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -40,9 +40,10 @@ namespace Avalonia.Media /// /// The name of the . /// The source of font resources. - public FontFamily(string name, Uri source) : this(name) + /// + public FontFamily(string name, Uri source, Uri baseUri = null) : this(name) { - Key = new FontFamilyKey(source); + Key = new FontFamilyKey(source, baseUri); } /// @@ -87,11 +88,12 @@ namespace Avalonia.Media /// Parses a string. /// /// The string. + /// /// /// /// Specified family is not supported. /// - public static FontFamily Parse(string s) + public static FontFamily Parse(string s, Uri baseUri = null) { if (string.IsNullOrEmpty(s)) { @@ -112,7 +114,13 @@ namespace Avalonia.Media case 2: { - return new FontFamily(segments[1], new Uri(segments[0], UriKind.RelativeOrAbsolute)); + var uri = s.StartsWith("/") + ? new Uri(s, UriKind.Relative) + : new Uri(s, UriKind.RelativeOrAbsolute); + + return uri.IsAbsoluteUri + ? new FontFamily(segments[1], uri) + : new FontFamily(segments[1], uri, baseUri); } default: diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs index 90ccac0e46..67b53cf8b4 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Linq; namespace Avalonia.Media.Fonts { @@ -12,48 +11,26 @@ namespace Avalonia.Media.Fonts public class FontFamilyKey { /// - /// Creates a new instance of and extracts and from given + /// Creates a new instance of /// /// - public FontFamilyKey(Uri source) + /// + public FontFamilyKey(Uri source, Uri baseUri = null) { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } + Source = source ?? throw new ArgumentNullException(nameof(source)); - if (source.AbsolutePath.Contains(".ttf")) - { - var filePathWithoutExtension = source.AbsolutePath.Replace(".ttf", string.Empty); - var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last(); - FileName = fileNameWithoutExtension + ".ttf"; - Location = new Uri(source.OriginalString.Replace("." + FileName, string.Empty), UriKind.RelativeOrAbsolute); - } - else - { - if (source.AbsolutePath.Contains(".otf")) - { - var filePathWithoutExtension = source.AbsolutePath.Replace(".otf", string.Empty); - var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last(); - FileName = fileNameWithoutExtension + ".otf"; - Location = new Uri(source.OriginalString.Replace("." + FileName, string.Empty), UriKind.RelativeOrAbsolute); - } - else - { - Location = source; - } - } + BaseUri = baseUri; } /// - /// Location of stored font asset that belongs to a + /// Source of stored font asset that belongs to a /// - public Uri Location { get; } + public Uri Source { get; } /// - /// Optional filename for a font asset that belongs to a + /// /// - public string FileName { get; } + public Uri BaseUri { get; } /// /// Returns a hash code for this instance. @@ -67,14 +44,14 @@ namespace Avalonia.Media.Fonts { var hash = (int)2166136261; - if (Location != null) + if (Source != null) { - hash = (hash * 16777619) ^ Location.GetHashCode(); + hash = (hash * 16777619) ^ Source.GetHashCode(); } - if (FileName != null) + if (BaseUri != null) { - hash = (hash * 16777619) ^ FileName.GetHashCode(); + hash = (hash * 16777619) ^ BaseUri.GetHashCode(); } return hash; @@ -95,12 +72,12 @@ namespace Avalonia.Media.Fonts return false; } - if (Location != other.Location) + if (Source != other.Source) { return false; } - if (FileName != other.FileName) + if (BaseUri != other.BaseUri) { return false; } @@ -116,16 +93,17 @@ namespace Avalonia.Media.Fonts /// public override string ToString() { - if (FileName == null) + if (Source.IsAbsoluteUri) { - return Location.PathAndQuery; + return Source.ToString(); } - var builder = new UriBuilder(Location); - - builder.Path += "." + FileName; + if (BaseUri != null) + { + return BaseUri + "/" + Source; + } - return builder.ToString(); + return Source.ToString(); } } } diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index 166eb4a661..e1eabf7237 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using Avalonia.Platform; namespace Avalonia.Media.Fonts @@ -20,21 +19,26 @@ namespace Avalonia.Media.Fonts public static IEnumerable LoadFontAssets(FontFamilyKey fontFamilyKey) { - return fontFamilyKey.FileName != null - ? GetFontAssetsByFileName(fontFamilyKey.Location, fontFamilyKey.FileName) - : GetFontAssetsByLocation(fontFamilyKey.Location); + if (fontFamilyKey.Source.OriginalString.Contains(".ttf") + || fontFamilyKey.Source.OriginalString.Contains(".otf")) + { + return GetFontAssetsByExpression(fontFamilyKey); + } + + return GetFontAssetsBySource(fontFamilyKey); } /// /// Searches for font assets at a given location and returns a quantity of found assets /// - /// + /// /// - private static IEnumerable GetFontAssetsByLocation(Uri location) + private static IEnumerable GetFontAssetsBySource(FontFamilyKey fontFamilyKey) { - var availableAssets = s_assetLoader.GetAssets(location, null); + var availableAssets = s_assetLoader.GetAssets(fontFamilyKey.Source, fontFamilyKey.BaseUri); - var matchingAssets = availableAssets.Where(x => x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf")); + var matchingAssets = + availableAssets.Where(x => x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf")); return matchingAssets; } @@ -43,20 +47,74 @@ namespace Avalonia.Media.Fonts /// Searches for font assets at a given location and only accepts assets that fit to a given filename expression. /// File names can target multiple files with * wildcard. For example "FontFile*.ttf" /// - /// - /// + /// /// - private static IEnumerable GetFontAssetsByFileName(Uri location, string fileName) + private static IEnumerable GetFontAssetsByExpression(FontFamilyKey fontFamilyKey) { - var availableResources = s_assetLoader.GetAssets(location, null); + var fileName = GetFileName(fontFamilyKey, out var fileExtension, out var location); + + var availableResources = s_assetLoader.GetAssets(location, fontFamilyKey.BaseUri); - var compareTo = location.AbsolutePath + "." + fileName.Split('*').First(); + string compareTo; - var matchingResources = - availableResources.Where(x => x.AbsolutePath.Contains(compareTo) && (x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf"))); + if (fontFamilyKey.Source.IsAbsoluteUri) + { + if (fontFamilyKey.Source.Scheme == "resm") + { + compareTo = location.AbsolutePath + "." + fileName.Split('*').First(); + } + else + { + compareTo = location.AbsolutePath + fileName.Split('*').First(); + } + } + else + { + compareTo = location.AbsolutePath + fileName.Split('*').First(); + } + + var matchingResources = availableResources.Where( + x => x.AbsolutePath.Contains(compareTo) + && x.AbsolutePath.EndsWith(fileExtension)); return matchingResources; } + private static string GetFileName(FontFamilyKey fontFamilyKey, out string fileExtension, out Uri location) + { + if (fontFamilyKey.Source.IsAbsoluteUri && fontFamilyKey.Source.Scheme == "resm") + { + fileExtension = "." + fontFamilyKey.Source.AbsolutePath.Split('.').LastOrDefault(); + + var fileName = fontFamilyKey.Source.LocalPath.Replace(fileExtension, string.Empty).Split('.').LastOrDefault(); + + location = new Uri(fontFamilyKey.Source.AbsoluteUri.Replace("." + fileName + fileExtension, string.Empty), UriKind.RelativeOrAbsolute); + + return fileName; + } + + var pathSegments = fontFamilyKey.Source.OriginalString.Split('/'); + + var fileNameWithExtension = pathSegments.Last().Split('#').First(); + + var fileNameSegments = fileNameWithExtension.Split('.'); + + fileExtension = "." + fileNameSegments.Last(); + + if (fontFamilyKey.BaseUri != null) + { + location = new Uri( + fontFamilyKey.BaseUri, + fontFamilyKey.Source.OriginalString.Split('#') + .First() + .Replace(fileNameWithExtension, string.Empty)); + } + else + { + location = new Uri(fontFamilyKey.Source.AbsolutePath.Replace(fileNameWithExtension, string.Empty)); + } + + return fileNameSegments.First(); + } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index ec73859a0a..bb3883285a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs index 93db025343..fa9d364fc0 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs @@ -11,6 +11,8 @@ using Avalonia.Controls.Templates; namespace Avalonia.Markup.Xaml { + using Avalonia.Media; + /// /// Maintains a repository of s for XAML parsing on top of those /// maintained by . @@ -37,8 +39,9 @@ namespace Avalonia.Markup.Xaml { typeof(Selector), typeof(SelectorTypeConverter) }, { typeof(TimeSpan), typeof(TimeSpanTypeConverter) }, { typeof(WindowIcon), typeof(IconTypeConverter) }, - { typeof(CultureInfo), typeof(CultureInfoConverter)}, - { typeof(Uri), typeof(AvaloniaUriTypeConverter)} + { typeof(CultureInfo), typeof(CultureInfoConverter) }, + { typeof(Uri), typeof(AvaloniaUriTypeConverter) }, + { typeof(FontFamily), typeof(FontFamilyTypeConverter) } }; /// @@ -84,4 +87,4 @@ namespace Avalonia.Markup.Xaml /// The converter type. Maybe be a non-constructed generic type. public static void Register(Type type, Type converterType) => _converters[type] = converterType; } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs new file mode 100644 index 0000000000..9b0e7a2b6b --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs @@ -0,0 +1,28 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.ComponentModel; +using System.Globalization; + +using Avalonia.Media; + +using Portable.Xaml.ComponentModel; + +namespace Avalonia.Markup.Xaml.Converters +{ + public class FontFamilyTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + var s = (string)value; + + return FontFamily.Parse(s, context.GetBaseUri()); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs index 96464b5784..38ad9e1866 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs @@ -22,7 +22,7 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts var fontFamilyKey = new FontFamilyKey(source); - Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Location); + Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Source); Assert.Null(fontFamilyKey.FileName); } @@ -34,7 +34,7 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts var fontFamilyKey = new FontFamilyKey(source); - Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Location); + Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Source); Assert.Equal("MyFont.ttf", fontFamilyKey.FileName); } From 54627eeff114f98cc231d7c788492ed40f0e4f16 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 27 Nov 2018 14:29:10 +0200 Subject: [PATCH 070/147] add failing unittest for immediaterenderer not rendering sometimes controls issue #2141 --- .../Rendering/ImmediateRendererTests.cs | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs index 82294246b1..73e4a14539 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs @@ -1,8 +1,11 @@ using System.Collections.Generic; using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.UnitTests; using Avalonia.VisualTree; using Moq; using Xunit; @@ -94,5 +97,86 @@ namespace Avalonia.Visuals.UnitTests.Rendering //then new position Assert.Equal(new Rect(100, 100, 100, 100), invalidationCalls[2]); } + + [Fact] + public void Should_Render_Child_In_Parent_With_RenderTransform() + { + var targetMock = new Mock() { CallBase = true }; + var target = targetMock.Object; + target.Width = 100; + target.Height = 50; + var child = new Panel() + { + RenderTransform = new RotateTransform() { Angle = 90 }, + Children = + { + new Panel() + { + Children = + { + target + } + } + } + }; + + var visualTarget = targetMock.As(); + int rendered = 0; + visualTarget.Setup(v => v.Render(It.IsAny())).Callback(() => rendered++); + + var root = new TestRoot(child); + root.Renderer = new ImmediateRenderer(root); + + root.LayoutManager.ExecuteInitialLayoutPass(root); + + root.Measure(new Size(50, 100)); + root.Arrange(new Rect(new Size(50, 100))); + + root.Renderer.Paint(root.Bounds); + + Assert.Equal(1, rendered); + } + + [Fact] + public void Should_Render_Child_In_Parent_With_RenderTransform2() + { + var targetMock = new Mock() { CallBase = true }; + var target = targetMock.Object; + + target.Width = 100; + target.Height = 50; + target.HorizontalAlignment = HorizontalAlignment.Center; + target.VerticalAlignment = VerticalAlignment.Center; + + var child = new Panel() + { + RenderTransform = new RotateTransform() { Angle = 90 }, + Children = + { + new Panel() + { + Children = + { + target + } + } + } + }; + + var visualTarget = targetMock.As(); + int rendered = 0; + visualTarget.Setup(v => v.Render(It.IsAny())).Callback(() => rendered++); + + var root = new TestRoot(child); + root.Renderer = new ImmediateRenderer(root); + + root.LayoutManager.ExecuteInitialLayoutPass(root); + + root.Measure(new Size(300, 100)); + root.Arrange(new Rect(new Size(300, 100))); + root.Renderer.Paint(root.Bounds); + + Assert.Equal(1, rendered); + } } } From 55600239a6f64ff2850a3afbc96b00b6750e00f5 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 27 Nov 2018 21:03:00 +0200 Subject: [PATCH 071/147] fix for immediaterenderer not rendering sometimes controls issue #2141 --- src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index 08f3803e9b..03ac004fd5 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -266,7 +266,14 @@ namespace Avalonia.Rendering if (clipToBounds) { - clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); + if (visual.RenderTransform != null) + { + clipRect = new Rect(visual.Bounds.Size); + } + else + { + clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); + } } using (context.PushPostTransform(m)) From 1d19e28e9b839faaa63f6e9bba0748227a168188 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 28 Nov 2018 14:17:50 +0100 Subject: [PATCH 072/147] Full support for relative uris --- samples/ControlCatalog/Pages/TextBoxPage.xaml | 4 ++-- src/Avalonia.Visuals/Media/FontFamily.cs | 6 +++--- src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs | 11 +++-------- .../Media/Fonts/FontFamilyLoader.cs | 13 ++++++------- .../Media/Fonts/FontFamilyKeyTests.cs | 6 +----- 5 files changed, 15 insertions(+), 25 deletions(-) diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index 67b60568e5..0c0a4d705b 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -44,8 +44,8 @@ res fonts - - + + diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index d32db4ef28..0ba25e6c0d 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -114,9 +114,9 @@ namespace Avalonia.Media case 2: { - var uri = s.StartsWith("/") - ? new Uri(s, UriKind.Relative) - : new Uri(s, UriKind.RelativeOrAbsolute); + var uri = segments[0].StartsWith("/") + ? new Uri(segments[0], UriKind.Relative) + : new Uri(segments[0], UriKind.RelativeOrAbsolute); return uri.IsAbsoluteUri ? new FontFamily(segments[1], uri) diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs index 67b53cf8b4..2803b942ad 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs @@ -28,7 +28,7 @@ namespace Avalonia.Media.Fonts public Uri Source { get; } /// - /// + /// A base URI to use if is relative /// public Uri BaseUri { get; } @@ -93,14 +93,9 @@ namespace Avalonia.Media.Fonts /// public override string ToString() { - if (Source.IsAbsoluteUri) + if (!Source.IsAbsoluteUri && BaseUri != null) { - return Source.ToString(); - } - - if (BaseUri != null) - { - return BaseUri + "/" + Source; + return string.Empty + BaseUri + Source; } return Source.ToString(); diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index e1eabf7237..623125164c 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -71,7 +71,7 @@ namespace Avalonia.Media.Fonts else { compareTo = location.AbsolutePath + fileName.Split('*').First(); - } + } var matchingResources = availableResources.Where( x => x.AbsolutePath.Contains(compareTo) @@ -95,7 +95,7 @@ namespace Avalonia.Media.Fonts var pathSegments = fontFamilyKey.Source.OriginalString.Split('/'); - var fileNameWithExtension = pathSegments.Last().Split('#').First(); + var fileNameWithExtension = pathSegments.Last(); var fileNameSegments = fileNameWithExtension.Split('.'); @@ -103,11 +103,10 @@ namespace Avalonia.Media.Fonts if (fontFamilyKey.BaseUri != null) { - location = new Uri( - fontFamilyKey.BaseUri, - fontFamilyKey.Source.OriginalString.Split('#') - .First() - .Replace(fileNameWithExtension, string.Empty)); + var relativePath = fontFamilyKey.Source.OriginalString + .Replace(fileNameWithExtension, string.Empty); + + location = new Uri(fontFamilyKey.BaseUri, relativePath); } else { diff --git a/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs index 38ad9e1866..b57ba781ed 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs @@ -23,8 +23,6 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts var fontFamilyKey = new FontFamilyKey(source); Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Source); - - Assert.Null(fontFamilyKey.FileName); } [Fact] @@ -34,9 +32,7 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts var fontFamilyKey = new FontFamilyKey(source); - Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Source); - - Assert.Equal("MyFont.ttf", fontFamilyKey.FileName); + Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests.MyFont.ttf"), fontFamilyKey.Source); } } } From d6c9ce3277cefa3754b8cc66ec44cb9da5af93aa Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 28 Nov 2018 17:15:40 +0100 Subject: [PATCH 073/147] Make sure only font files are compared --- src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs | 2 +- src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs index 2803b942ad..76ee2b7aad 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs @@ -95,7 +95,7 @@ namespace Avalonia.Media.Fonts { if (!Source.IsAbsoluteUri && BaseUri != null) { - return string.Empty + BaseUri + Source; + return BaseUri.Authority + Source; } return Source.ToString(); diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index 623125164c..1e1049d017 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -19,8 +19,10 @@ namespace Avalonia.Media.Fonts public static IEnumerable LoadFontAssets(FontFamilyKey fontFamilyKey) { - if (fontFamilyKey.Source.OriginalString.Contains(".ttf") - || fontFamilyKey.Source.OriginalString.Contains(".otf")) + var sourceWithoutArguments = fontFamilyKey.Source.OriginalString.Split('?').First(); + + if (sourceWithoutArguments.EndsWith(".ttf") + || sourceWithoutArguments.EndsWith(".otf")) { return GetFontAssetsByExpression(fontFamilyKey); } From bd97346b97ad76c00742b7dd6805c3d00105fe57 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 28 Nov 2018 17:18:06 +0100 Subject: [PATCH 074/147] Fix missing comment --- src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index 1e1049d017..063fe8f20d 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -17,6 +17,11 @@ namespace Avalonia.Media.Fonts s_assetLoader = AvaloniaLocator.Current.GetService(); } + /// + /// Loads all font assets that belong to the specified + /// + /// + /// public static IEnumerable LoadFontAssets(FontFamilyKey fontFamilyKey) { var sourceWithoutArguments = fontFamilyKey.Source.OriginalString.Split('?').First(); From c4cc3514a8c35f36230f8902bbddbf7bffaf624f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 28 Nov 2018 16:57:49 +0000 Subject: [PATCH 075/147] Set win32 WindowStyle flags individually. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 17b72955bc..bdf58e51ee 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -265,13 +265,18 @@ namespace Avalonia.Win32 return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; - - UnmanagedMethods.RECT windowRect; - - UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); + if (value) + { + style |= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU & WindowStyles.WS_SIZEFRAME); + } + else + { + style ^= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU & WindowStyles.WS_SIZEFRAME); + } + + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); Rect newRect; var oldThickness = BorderThickness; @@ -436,7 +441,7 @@ namespace Avalonia.Win32 0, atom, null, - (int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW, + (int)(WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU & WindowStyles.WS_SIZEFRAME), UnmanagedMethods.CW_USEDEFAULT, UnmanagedMethods.CW_USEDEFAULT, UnmanagedMethods.CW_USEDEFAULT, From 743e61cfde37e608820e1d6c7e0671a5e22e781e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 28 Nov 2018 16:57:49 +0000 Subject: [PATCH 076/147] Revert "Set win32 WindowStyle flags individually." This reverts commit c4cc3514a8c35f36230f8902bbddbf7bffaf624f. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index bdf58e51ee..17b72955bc 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -265,18 +265,13 @@ namespace Avalonia.Win32 return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - if (value) - { - style |= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU & WindowStyles.WS_SIZEFRAME); - } - else - { - style ^= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU & WindowStyles.WS_SIZEFRAME); - } - - UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); + style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + + UnmanagedMethods.RECT windowRect; + + UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); Rect newRect; var oldThickness = BorderThickness; @@ -441,7 +436,7 @@ namespace Avalonia.Win32 0, atom, null, - (int)(WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU & WindowStyles.WS_SIZEFRAME), + (int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW, UnmanagedMethods.CW_USEDEFAULT, UnmanagedMethods.CW_USEDEFAULT, UnmanagedMethods.CW_USEDEFAULT, From c8515ebea094c0798fd59404957dc7611ff34aaf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 28 Nov 2018 17:17:46 +0000 Subject: [PATCH 077/147] handle window styles so that it obeys can resize. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 26 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 17b72955bc..e8f9f37087 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -269,6 +269,17 @@ namespace Avalonia.Win32 style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + if (!value) + { + style ^= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU); + + if (!_resizable) + { + style ^= (UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + } + } + + UnmanagedMethods.RECT windowRect; UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); @@ -916,17 +927,14 @@ namespace Avalonia.Win32 return; } - if (_decorated) - { - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - if (value) - style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; - else - style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + if (value) + style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; + else + style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME); - UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); - } + UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); _resizable = value; } From ea8bde211b57bfdfabfdd0ddf7452f6e3ce52c4c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 29 Nov 2018 11:31:07 +0300 Subject: [PATCH 078/147] Additional `Start` method overload --- samples/ControlCatalog.NetCore/Program.cs | 10 ++++++++-- src/Avalonia.Controls/AppBuilderBase.cs | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 1f53dedc14..57c8b700df 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -23,6 +23,7 @@ namespace ControlCatalog.NetCore break; } } + if (args.Contains("--fbdev")) AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => { @@ -30,7 +31,12 @@ namespace ControlCatalog.NetCore System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); }); else - BuildAvaloniaApp().Start(); + BuildAvaloniaApp().Start(AppMain, args); + } + + static void AppMain(Application app, string[] args) + { + app.Run(new MainWindow()); } /// @@ -46,4 +52,4 @@ namespace ControlCatalog.NetCore Console.ReadKey(true); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 376714b20b..c5dd072d8a 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -145,6 +145,15 @@ namespace Avalonia.Controls Instance.Run(mainWindow); } + public delegate void AppMainDelegate(Application app, string[] args); + + public void Start(AppMainDelegate main, string[] args) + { + Setup(); + BeforeStartCallback(Self); + main(Instance, args); + } + /// /// Sets up the platform-specific services for the application, but does not run it. /// From 053e6a2be57006efd9be9aea783bd1d311cc40a6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Nov 2018 13:02:40 +0000 Subject: [PATCH 079/147] update wmstyles in single place, and implement nccalc size message. --- .../Interop/UnmanagedMethods.cs | 20 ++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 113 +++++++++--------- 2 files changed, 79 insertions(+), 54 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 9e8ff20e44..adfbf0cb52 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1181,6 +1181,26 @@ namespace Avalonia.Win32.Interop } } + [StructLayout(LayoutKind.Sequential)] + public struct WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndInsertAfter; + public int x; + public int y; + public int cx; + public int cy; + public uint flags; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NCCALCSIZE_PARAMS + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public RECT[] rgrc; + public WINDOWPOS lppos; + } + public struct TRACKMOUSEEVENT { public int cbSize; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index e8f9f37087..fbd0752a19 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -35,6 +35,7 @@ namespace Avalonia.Win32 private bool _decorated = true; private bool _resizable = true; private bool _topmost = false; + private bool _taskbarIcon = true; private double _scaling = 1; private WindowState _showWindowState; private WindowState _lastWindowState; @@ -96,7 +97,8 @@ namespace Avalonia.Win32 { var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); - var padding = new UnmanagedMethods.RECT(); + + var padding = new RECT(); if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle)) { @@ -265,66 +267,27 @@ namespace Avalonia.Win32 return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - - style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; - - if (!value) - { - style ^= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU); - - if (!_resizable) - { - style ^= (UnmanagedMethods.WindowStyles.WS_SIZEFRAME); - } - } - - - UnmanagedMethods.RECT windowRect; - - UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); + _decorated = value; - Rect newRect; + UpdateWMStyles(); + + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); + var oldThickness = BorderThickness; - - UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); var thickness = BorderThickness; _decorated = value; - newRect = new Rect( + var newRect = new Rect( windowRect.left - thickness.Left, windowRect.top - thickness.Top, (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - if(!value) - { - RECT rc = new RECT(); - uint style1 = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - uint styleEx1 = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); - UnmanagedMethods.AdjustWindowRectEx(ref rc, style1, false, styleEx1); - } - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, (int)newRect.Height, UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); - - if(_decorated) - { - if (_resizable) - { - // If we switch decorations back on we need to restore WS_SizeFrame. - _resizable = false; - CanResize(true); - } - else - { - _resizable = true; - CanResize(false); - } - } } public void Invalidate(Rect rect) @@ -490,8 +453,23 @@ namespace Avalonia.Win32 return IntPtr.Zero; case WindowsMessage.WM_NCCALCSIZE: - if (!_decorated) + if (ToInt32(wParam) == 1 && !_decorated) { + + // Calculate new NCCALCSIZE_PARAMS based on custom NCA inset. + var pncsp = Marshal.PtrToStructure(lParam); + + pncsp.rgrc[0].left = pncsp.rgrc[0].left + 0; + pncsp.rgrc[0].top = pncsp.rgrc[0].top + 0; + pncsp.rgrc[0].right = pncsp.rgrc[0].right - 0; + pncsp.rgrc[0].bottom = pncsp.rgrc[0].bottom - 0; + + //lRet = 0; + + // No need to pass the message on to the DefWindowProc. + //fCallDWP = false; + + return IntPtr.Zero; } break; @@ -897,14 +875,23 @@ namespace Avalonia.Win32 return (int)(ptr.ToInt64() & 0xffffffff); } + public void ShowTaskbarIcon(bool value) { + if(_taskbarIcon == value) + { + return; + } + + _taskbarIcon = value; + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE); style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW; + if (value) style |= UnmanagedMethods.WindowStyles.WS_EX_APPWINDOW; else @@ -920,23 +907,41 @@ namespace Avalonia.Win32 } } - public void CanResize(bool value) + private void UpdateWMStyles() { - if (value == _resizable) + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + + style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + + if (!_decorated) { - return; + style ^= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU); } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - - if (value) + if (_resizable) + { style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; + } else - style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + { + style ^= (UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + } UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); + var thickness = BorderThickness; + } + + public void CanResize(bool value) + { + if (value == _resizable) + { + return; + } + _resizable = value; + + UpdateWMStyles(); } public void SetTopmost(bool value) From 1c871bf423db1d2d777cc5ca53de229e5c5787bb Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Nov 2018 13:42:22 +0000 Subject: [PATCH 080/147] [win32] fix maximize borders and wm window styles for non-decorated windows. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index fbd0752a19..d32d2be1a6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -455,21 +455,6 @@ namespace Avalonia.Win32 case WindowsMessage.WM_NCCALCSIZE: if (ToInt32(wParam) == 1 && !_decorated) { - - // Calculate new NCCALCSIZE_PARAMS based on custom NCA inset. - var pncsp = Marshal.PtrToStructure(lParam); - - pncsp.rgrc[0].left = pncsp.rgrc[0].left + 0; - pncsp.rgrc[0].top = pncsp.rgrc[0].top + 0; - pncsp.rgrc[0].right = pncsp.rgrc[0].right - 0; - pncsp.rgrc[0].bottom = pncsp.rgrc[0].bottom - 0; - - //lRet = 0; - - // No need to pass the message on to the DefWindowProc. - //fCallDWP = false; - - return IntPtr.Zero; } break; @@ -915,7 +900,7 @@ namespace Avalonia.Win32 if (!_decorated) { - style ^= (WindowStyles.WS_CAPTION & WindowStyles.WS_MINIMIZEBOX & WindowStyles.WS_MAXIMIZEBOX & WindowStyles.WS_SYSMENU); + style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_MAXIMIZEBOX | WindowStyles.WS_SYSMENU); } if (_resizable) From 2572e0d0656cb230472fe1052b20234c5c588edb Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Nov 2018 14:02:41 +0000 Subject: [PATCH 081/147] fix rendering issues when clicking on modal dialogs parent and dialog is non-decorated. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index d32d2be1a6..e49eb5be9f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -619,6 +619,14 @@ namespace Avalonia.Win32 new Point(0, 0), GetMouseModifiers(wParam)); break; + case WindowsMessage.WM_NCPAINT: + case WindowsMessage.WM_NCACTIVATE: + if (!_decorated) + { + return IntPtr.Zero; + } + break; + case UnmanagedMethods.WindowsMessage.WM_PAINT: UnmanagedMethods.PAINTSTRUCT ps; if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero) From 658ff21d0ff97cac034ff416b4f73d948fc71aff Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Nov 2018 14:03:00 +0000 Subject: [PATCH 082/147] fix border thickness being added to non-decorated windows. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index e49eb5be9f..932cb4a9d0 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -149,7 +149,11 @@ namespace Avalonia.Win32 if (value != ClientSize) { value *= Scaling; - value += BorderThickness; + + if (_decorated) + { + value += BorderThickness; + } UnmanagedMethods.SetWindowPos( _hwnd, From 374d6a5f0d06412e0b194915aefdafea22079375 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Nov 2018 14:15:24 +0000 Subject: [PATCH 083/147] fix smooth toggling between decorated and non-decorated. --- samples/ControlCatalog/DecoratedWindow.xaml | 6 +++- src/Windows/Avalonia.Win32/WindowImpl.cs | 31 +++++++++++++++------ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index 37014255cc..fdb18fcca2 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -3,7 +3,7 @@ x:Class="ControlCatalog.DecoratedWindow" Title="Avalonia Control Gallery" Icon="/Assets/test_icon.ico" - xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False"> + xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> @@ -20,7 +20,11 @@ + Hello world! + + Decorated + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 932cb4a9d0..5fa399e2a9 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -279,23 +279,36 @@ namespace Avalonia.Win32 var oldThickness = BorderThickness; - var thickness = BorderThickness; - _decorated = value; - var newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + Rect newRect; + + if (value) + { + var thickness = BorderThickness; + + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + { + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + } UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); } public void Invalidate(Rect rect) - { + { var f = Scaling; var r = new UnmanagedMethods.RECT { From 9050409afcfa20fb00c3b2b26b8c5e588e02dd9c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 14:28:03 +0000 Subject: [PATCH 084/147] [win32] windowimpl update style calculations done in one place. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 111 ++++++++++++----------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5fa399e2a9..621de88019 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -271,44 +271,11 @@ namespace Avalonia.Win32 return; } - _decorated = value; - - UpdateWMStyles(); - - UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - - var oldThickness = BorderThickness; - - _decorated = value; - - Rect newRect; - - if (value) - { - var thickness = BorderThickness; - - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } - - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + UpdateWMStyles(() => _decorated = value); } public void Invalidate(Rect rect) - { + { var f = Scaling; var r = new UnmanagedMethods.RECT { @@ -474,7 +441,7 @@ namespace Avalonia.Win32 { return IntPtr.Zero; } - break; + break; case UnmanagedMethods.WindowsMessage.WM_CLOSE: bool? preventClosing = Closing?.Invoke(); @@ -686,7 +653,7 @@ namespace Avalonia.Win32 MINMAXINFO mmi = Marshal.PtrToStructure(lParam); - if (_minSize.Width > 0) + if (_minSize.Width > 0) mmi.ptMinTrackSize.X = (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); if (_minSize.Height > 0) @@ -857,7 +824,7 @@ namespace Avalonia.Win32 { MONITORINFO monitorInfo = MONITORINFO.Create(); - if (GetMonitorInfo(monitor,ref monitorInfo)) + if (GetMonitorInfo(monitor, ref monitorInfo)) { RECT rcMonitorArea = monitorInfo.rcMonitor; @@ -885,17 +852,17 @@ namespace Avalonia.Win32 return (int)(ptr.ToInt64() & 0xffffffff); } - + public void ShowTaskbarIcon(bool value) { - if(_taskbarIcon == value) + if (_taskbarIcon == value) { return; } _taskbarIcon = value; - + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE); @@ -917,29 +884,69 @@ namespace Avalonia.Win32 } } - private void UpdateWMStyles() + private void UpdateWMStyles(Action changer) { - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var decorated = _decorated; + + var resizable = _resizable; - style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + changer(); - if (!_decorated) + var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); + + if (decorated != _decorated) { - style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_MAXIMIZEBOX | WindowStyles.WS_SYSMENU); + style |= WindowStyles.WS_OVERLAPPEDWINDOW; + + if (!_decorated) + { + style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU); + } } if (_resizable) { - style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; + style |= WindowStyles.WS_SIZEFRAME; } else { - style ^= (UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + style ^= (WindowStyles.WS_SIZEFRAME); } - UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); + SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); + + if (decorated != _decorated) + { + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); + + var oldThickness = BorderThickness; + + Rect newRect; + + if (_decorated) + { + var thickness = BorderThickness; - var thickness = BorderThickness; + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + { + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + } + + UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, + (int)newRect.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + + } } public void CanResize(bool value) @@ -949,9 +956,7 @@ namespace Avalonia.Win32 return; } - _resizable = value; - - UpdateWMStyles(); + UpdateWMStyles(() => _resizable = value); } public void SetTopmost(bool value) From c9b069ffb74a44536059bf88db1922d637a2ca6f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 14:34:42 +0000 Subject: [PATCH 085/147] simplify style flag calculation. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 621de88019..8343ccbd44 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -892,23 +892,14 @@ namespace Avalonia.Win32 changer(); - var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); + var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE) | WindowStyles.WS_OVERLAPPEDWINDOW; - if (decorated != _decorated) + if (!_decorated) { - style |= WindowStyles.WS_OVERLAPPEDWINDOW; - - if (!_decorated) - { - style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU); - } + style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU); } - if (_resizable) - { - style |= WindowStyles.WS_SIZEFRAME; - } - else + if (!_resizable) { style ^= (WindowStyles.WS_SIZEFRAME); } From dbacf80511f5dd0ee18cb97b3b81e656fb4fc339 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 14:53:19 +0000 Subject: [PATCH 086/147] tidy decorated window. --- samples/ControlCatalog/DecoratedWindow.xaml | 65 ++++++++++----------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index fdb18fcca2..9f05cd1f57 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -1,39 +1,38 @@ - - - - - Title - - - - - - - - - - - - Hello world! + + + + Title + + + + + + + + + + + + Hello world! - Decorated - - - - - - - - - - - - + Decorated + + + + + + + + + + + + From f8d3046cb59d09107d112aa801810a6bfffb9893 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 1 Dec 2018 17:54:32 +0300 Subject: [PATCH 087/147] Switched build to Nuke --- .nuke | 0 .travis.yml | 2 +- Avalonia.sln | 6 + appveyor.yml | 2 +- azure-pipelines.yml | 25 ++- build.cake | 312 ---------------------------- build.ps1 | 226 +++++--------------- build.sh | 139 +++++-------- cake.config | 15 -- nukebuild/Build.cs | 278 +++++++++++++++++++++++++ nukebuild/BuildParameters.cs | 186 +++++++++++++++++ nukebuild/Shims.cs | 80 +++++++ nukebuild/_build.csproj | 35 ++++ nukebuild/_build.csproj.DotSettings | 24 +++ 14 files changed, 723 insertions(+), 607 deletions(-) create mode 100644 .nuke delete mode 100644 build.cake delete mode 100644 cake.config create mode 100644 nukebuild/Build.cs create mode 100644 nukebuild/BuildParameters.cs create mode 100644 nukebuild/Shims.cs create mode 100644 nukebuild/_build.csproj create mode 100644 nukebuild/_build.csproj.DotSettings diff --git a/.nuke b/.nuke new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.travis.yml b/.travis.yml index b0c0c807cb..d60323b418 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ dotnet: 2.1.200 script: - sudo apt-get update - sudo apt-get install castxml - - ./build.sh --target "Travis" --configuration "Release" + - ./build.sh --target "CiTravis" --configuration "Release" notifications: email: false webhooks: diff --git a/Avalonia.sln b/Avalonia.sln index df60ff4a75..e006cadb5a 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -191,6 +191,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Desktop", "src\Ava EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.csproj", "{3F00BC43-5095-477F-93D8-E65B08179A00}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -216,6 +218,10 @@ Global Release|iPhoneSimulator = Release|iPhoneSimulator EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.Build.0 = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU diff --git a/appveyor.yml b/appveyor.yml index 484fb4586f..8694495e66 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,7 +14,7 @@ init: before_build: - git submodule update --init build_script: -- ps: .\build.ps1 -Target "AppVeyor" -Configuration "$env:configuration" +- ps: .\build.ps1 --target "CiAppVeyor" --configuration "$env:configuration" test: off artifacts: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f6929f8dee..dec361affe 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -11,19 +11,18 @@ jobs: sudo apt-get install castxml - task: CmdLine@2 - displayName: 'Install Cake' + displayName: 'Install Nuke' inputs: script: | - dotnet tool install -g Cake.Tool --version 0.30.0 - + dotnet tool install --global Nuke.GlobalTool --version 0.12.3 - task: CmdLine@2 - displayName: 'Run Cake' + displayName: 'Run Nuke' inputs: script: | export PATH="$PATH:$HOME/.dotnet/tools" dotnet --info printenv - dotnet cake build.cake -target="Azure-Linux" -configuration="Release" + nuke --target="CiAzureLinux" --configuration="Release" - task: PublishTestResults@2 inputs: @@ -55,13 +54,13 @@ jobs: script: brew install castxml - task: CmdLine@2 - displayName: 'Install Cake' + displayName: 'Install Nuke' inputs: script: | - dotnet tool install -g Cake.Tool --version 0.30.0 + dotnet tool install --global Nuke.GlobalTool --version 0.12.3 - task: CmdLine@2 - displayName: 'Run Cake' + displayName: 'Run Nuke' inputs: script: | export COREHOST_TRACE=0 @@ -72,7 +71,7 @@ jobs: export PATH="$PATH:$HOME/.dotnet/tools" dotnet --info printenv - dotnet cake build.cake -target="Azure-OSX" -configuration="Release" + nuke --target="CiAzureOSX" --configuration="Release" - task: PublishTestResults@2 inputs: @@ -97,17 +96,17 @@ jobs: vmImage: 'vs2017-win2016' steps: - task: CmdLine@2 - displayName: 'Install Cake' + displayName: 'Install Nuke' inputs: script: | - dotnet tool install -g Cake.Tool --version 0.30.0 + dotnet tool install --global Nuke.GlobalTool --version 0.12.3 - task: CmdLine@2 - displayName: 'Run Cake' + displayName: 'Run Nuke' inputs: script: | set PATH=%PATH%;%USERPROFILE%\.dotnet\tools - dotnet cake build.cake -target="Azure-Windows" -configuration="Release" + nuke --target="CiAzureWindows" --configuration="Release" - task: PublishTestResults@2 inputs: diff --git a/build.cake b/build.cake deleted file mode 100644 index f10a12c4e6..0000000000 --- a/build.cake +++ /dev/null @@ -1,312 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// TOOLS -/////////////////////////////////////////////////////////////////////////////// - -#tool "nuget:?package=NuGet.CommandLine&version=4.7.1" -#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.3" -#tool "nuget:?package=xunit.runner.console&version=2.3.1" -#tool "nuget:?package=JetBrains.dotMemoryUnit&version=3.0.20171219.105559" - -/////////////////////////////////////////////////////////////////////////////// -// USINGS -/////////////////////////////////////////////////////////////////////////////// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -/////////////////////////////////////////////////////////////////////////////// -// SCRIPTS -/////////////////////////////////////////////////////////////////////////////// - -#load "./parameters.cake" - -/////////////////////////////////////////////////////////////////////////////// -// SETUP -/////////////////////////////////////////////////////////////////////////////// - -Setup(context => -{ - var parameters = new Parameters(context); - - Information("Building version {0} of Avalonia ({1}) using version {2} of Cake.", - parameters.Version, - parameters.Configuration, - typeof(ICakeContext).Assembly.GetName().Version.ToString()); - - if (parameters.IsRunningOnAppVeyor) - { - Information("Repository Name: " + BuildSystem.AppVeyor.Environment.Repository.Name); - Information("Repository Branch: " + BuildSystem.AppVeyor.Environment.Repository.Branch); - } - Information("Target:" + context.TargetTask.Name); - Information("Configuration: " + parameters.Configuration); - Information("IsLocalBuild: " + parameters.IsLocalBuild); - Information("IsRunningOnUnix: " + parameters.IsRunningOnUnix); - Information("IsRunningOnWindows: " + parameters.IsRunningOnWindows); - Information("IsRunningOnAppVeyor: " + parameters.IsRunningOnAppVeyor); - Information("IsRunnongOnAzure:" + parameters.IsRunningOnAzure); - Information("IsPullRequest: " + parameters.IsPullRequest); - Information("IsMainRepo: " + parameters.IsMainRepo); - Information("IsMasterBranch: " + parameters.IsMasterBranch); - Information("IsReleaseBranch: " + parameters.IsReleaseBranch); - Information("IsTagged: " + parameters.IsTagged); - Information("IsReleasable: " + parameters.IsReleasable); - Information("IsMyGetRelease: " + parameters.IsMyGetRelease); - Information("IsNuGetRelease: " + parameters.IsNuGetRelease); - - return parameters; -}); - -/////////////////////////////////////////////////////////////////////////////// -// TEARDOWN -/////////////////////////////////////////////////////////////////////////////// - -Teardown((context, buildContext) => -{ - Information("Finished running tasks."); -}); - -/////////////////////////////////////////////////////////////////////////////// -// TASKS -/////////////////////////////////////////////////////////////////////////////// - -Task("Clean-Impl") - .Does(data => -{ - CleanDirectories(data.BuildDirs); - CleanDirectory(data.ArtifactsDir); - CleanDirectory(data.NugetRoot); - CleanDirectory(data.ZipRoot); - CleanDirectory(data.TestResultsRoot); -}); - -void DotNetCoreBuild(Parameters parameters) -{ - var settings = new DotNetCoreBuildSettings - { - Configuration = parameters.Configuration, - MSBuildSettings = new DotNetCoreMSBuildSettings - { - Properties = - { - { "PackageVersion", new [] { parameters.Version } } - } - } - }; - - DotNetCoreBuild(parameters.MSBuildSolution, settings); -} - -Task("Build-Impl") - .Does(data => -{ - if(data.IsRunningOnWindows) - { - MSBuild(data.MSBuildSolution, settings => { - settings.SetConfiguration(data.Configuration); - settings.SetVerbosity(Verbosity.Minimal); - settings.WithProperty("iOSRoslynPathHackRequired", "true"); - settings.WithProperty("PackageVersion", data.Version); - settings.UseToolVersion(MSBuildToolVersion.VS2017); - settings.WithRestore(); - }); - } - else - { - DotNetCoreBuild(data); - } -}); - -void RunCoreTest(string project, Parameters parameters, bool coreOnly = false) -{ - if(!project.EndsWith(".csproj")) - project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj"); - Information("Running tests from " + project); - var frameworks = new List(){"netcoreapp2.0"}; - foreach(var fw in frameworks) - { - if(!fw.StartsWith("netcoreapp") && coreOnly) - continue; - Information("Running for " + fw); - - var settings = new DotNetCoreTestSettings { - Configuration = parameters.Configuration, - Framework = fw, - NoBuild = true, - NoRestore = true - }; - - if (parameters.PublishTestResults) - { - settings.Logger = "trx"; - settings.ResultsDirectory = parameters.TestResultsRoot; - } - - DotNetCoreTest(project, settings); - } -} - -Task("Run-Unit-Tests-Impl") - .WithCriteria((context, data) => !data.SkipTests) - .Does(data => -{ - RunCoreTest("./tests/Avalonia.Base.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Controls.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Input.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Layout.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Markup.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Styling.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.Skia.UnitTests", data, false); - RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", data, false); - if (data.IsRunningOnWindows) - { - RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data, false); - } -}); - -Task("Run-Designer-Tests-Impl") - .WithCriteria((context, data) => !data.SkipTests) - .Does(data => -{ - RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", data, false); -}); - -Task("Run-Render-Tests-Impl") - .WithCriteria((context, data) => !data.SkipTests) - .WithCriteria((context, data) => data.IsRunningOnWindows) - .Does(data => -{ - RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", data, true); - RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", data, true); -}); - -Task("Run-Leak-Tests-Impl") - .WithCriteria((context, data) => !data.SkipTests) - .WithCriteria((context, data) => data.IsRunningOnWindows) - .Does(() => -{ - var dotMemoryUnit = Context.Tools.Resolve("dotMemoryUnit.exe"); - var leakTestsExitCode = StartProcess(dotMemoryUnit, new ProcessSettings - { - Arguments = new ProcessArgumentBuilder() - .Append(Context.Tools.Resolve("xunit.console.x86.exe").FullPath) - .Append("--propagate-exit-code") - .Append("--") - .Append("tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll"), - Timeout = 120000 - }); - - if (leakTestsExitCode != 0) - { - throw new Exception("Leak Tests failed"); - } -}); - -Task("Zip-Files-Impl") - .Does(data => -{ - Zip(data.BinRoot, data.ZipCoreArtifacts); - - Zip(data.NugetRoot, data.ZipNuGetArtifacts); - - Zip(data.ZipSourceControlCatalogDesktopDirs, - data.ZipTargetControlCatalogDesktopDirs, - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") + - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") + - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") + - GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe")); -}); - -void DotNetCorePack(Parameters parameters) -{ - var settings = new DotNetCorePackSettings - { - Configuration = parameters.Configuration, - MSBuildSettings = new DotNetCoreMSBuildSettings - { - Properties = - { - { "PackageVersion", new [] { parameters.Version } } - } - } - }; - - DotNetCorePack(parameters.MSBuildSolution, settings); -} - -Task("Create-NuGet-Packages-Impl") - .Does(data => -{ - if(data.IsRunningOnWindows) - { - MSBuild(data.MSBuildSolution, settings => { - settings.SetConfiguration(data.Configuration); - settings.SetVerbosity(Verbosity.Minimal); - settings.WithProperty("iOSRoslynPathHackRequired", "true"); - settings.WithProperty("PackageVersion", data.Version); - settings.UseToolVersion(MSBuildToolVersion.VS2017); - settings.WithRestore(); - settings.WithTarget("Pack"); - }); - } - else - { - DotNetCorePack(data); - } -}); - -/////////////////////////////////////////////////////////////////////////////// -// TARGETS -/////////////////////////////////////////////////////////////////////////////// - -Task("Build") - .IsDependentOn("Clean-Impl") - .IsDependentOn("Build-Impl"); - -Task("Run-Tests") - .IsDependentOn("Build") - .IsDependentOn("Run-Unit-Tests-Impl") - .IsDependentOn("Run-Render-Tests-Impl") - .IsDependentOn("Run-Designer-Tests-Impl") - .IsDependentOn("Run-Leak-Tests-Impl"); - -Task("Package") - .IsDependentOn("Run-Tests") - .IsDependentOn("Create-NuGet-Packages-Impl"); - -Task("AppVeyor") - .IsDependentOn("Package") - .IsDependentOn("Zip-Files-Impl"); - -Task("Travis") - .IsDependentOn("Run-Tests"); - -Task("Azure-Linux") - .IsDependentOn("Run-Tests"); - -Task("Azure-OSX") - .IsDependentOn("Package") - .IsDependentOn("Zip-Files-Impl"); - -Task("Azure-Windows") - .IsDependentOn("Package") - .IsDependentOn("Zip-Files-Impl"); - -/////////////////////////////////////////////////////////////////////////////// -// EXECUTE -/////////////////////////////////////////////////////////////////////////////// - -var target = Context.Argument("target", "Default"); - -if (target == "Default") -{ - target = Context.IsRunningOnWindows() ? "Package" : "Run-Tests"; -} - -RunTarget(target); diff --git a/build.ps1 b/build.ps1 index 46696db2b2..57e2f80075 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,201 +1,69 @@ -########################################################################## -# This is the Cake bootstrapper script for PowerShell. -# This file was downloaded from https://github.com/cake-build/resources -# Feel free to change this file to fit your needs. -########################################################################## - -<# - -.SYNOPSIS -This is a Powershell script to bootstrap a Cake build. - -.DESCRIPTION -This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) -and execute your Cake build script with the parameters you provide. - -.PARAMETER Script -The build script to execute. -.PARAMETER Target -The build script target to run. -.PARAMETER Platform -The build platform to use. -.PARAMETER Configuration -The build configuration to use. -.PARAMETER Verbosity -Specifies the amount of information to be displayed. -.PARAMETER Experimental -Tells Cake to use the latest Roslyn release. -.PARAMETER WhatIf -Performs a dry run of the build script. -No tasks will be executed. -.PARAMETER Mono -Tells Cake to use the Mono scripting engine. -.PARAMETER SkipToolPackageRestore -Skips restoring of packages. -.PARAMETER SkipTests -Skips unit tests -.PARAMETER ScriptArgs -Remaining arguments are added here. - -.LINK -http://cakebuild.net - -#> - [CmdletBinding()] Param( - [string]$Script = "build.cake", - [string]$Target = "Default", - [ValidateSet("Any CPU", "x86", "x64", "NetCoreOnly", "iPhone")] - [string]$Platform = "Any CPU", - [ValidateSet("Release", "Debug")] - [string]$Configuration = "Release", - [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] - [string]$Verbosity = "Verbose", - [switch]$Experimental, - [Alias("DryRun","Noop")] - [switch]$WhatIf, - [switch]$Mono, - [switch]$SkipToolPackageRestore, + #[switch]$CustomParam, [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] - [string[]]$ScriptArgs + [string[]]$BuildArguments ) -[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null -function MD5HashFile([string] $filePath) -{ - if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) - { - return $null - } - - [System.IO.Stream] $file = $null; - [System.Security.Cryptography.MD5] $md5 = $null; - try - { - $md5 = [System.Security.Cryptography.MD5]::Create() - $file = [System.IO.File]::OpenRead($filePath) - return [System.BitConverter]::ToString($md5.ComputeHash($file)) - } - finally - { - if ($file -ne $null) - { - $file.Dispose() - } - } -} - -Write-Host "Preparing to run build script..." - -if(!$PSScriptRoot){ - $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent -} +Write-Output "Windows PowerShell $($Host.Version)" -$TOOLS_DIR = Join-Path $PSScriptRoot "tools" -$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" -$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" -$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" -$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" +Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { exit 1 } +$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent -# Should we use mono? -$UseMono = ""; -if($Mono.IsPresent) { - Write-Verbose -Message "Using the Mono based scripting engine." - $UseMono = "-mono" -} +########################################################################### +# CONFIGURATION +########################################################################### -# Should we use the new Roslyn? -$UseExperimental = ""; -if($Experimental.IsPresent -and !($Mono.IsPresent)) { - Write-Verbose -Message "Using experimental version of Roslyn." - $UseExperimental = "-experimental" -} +$BuildProjectFile = "$PSScriptRoot\nukebuild\_build.csproj" +$TempDirectory = "$PSScriptRoot\\.tmp" -# Is this a dry run? -$UseDryRun = ""; -if($WhatIf.IsPresent) { - $UseDryRun = "-dryrun" -} +$DotNetGlobalFile = "$PSScriptRoot\\global.json" +$DotNetInstallUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.ps1" +$DotNetChannel = "Current" -# Is this a dry run? -$UseSkipTests = ""; -if($SkipTests.IsPresent) { - $UseSkipTests = "-skip-tests" -} +$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 +$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 +$env:NUGET_XMLDOC_MODE = "skip" -# Make sure tools folder exists -if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { - Write-Verbose -Message "Creating tools directory..." - New-Item -Path $TOOLS_DIR -Type directory | out-null -} +########################################################################### +# EXECUTION +########################################################################### -# Make sure that packages.config exist. -if (!(Test-Path $PACKAGES_CONFIG)) { - Write-Verbose -Message "Downloading packages.config..." - try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { - Throw "Could not download packages.config." - } +function ExecSafe([scriptblock] $cmd) { + & $cmd + if ($LASTEXITCODE) { exit $LASTEXITCODE } } -# Try find NuGet.exe in path if not exists -if (!(Test-Path $NUGET_EXE)) { - Write-Verbose -Message "Trying to find nuget.exe in PATH..." - $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) } - $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 - if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { - Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." - $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName +# If global.json exists, load expected version +if (Test-Path $DotNetGlobalFile) { + $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) + if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { + $DotNetVersion = $DotNetGlobal.sdk.version } } -# Try download NuGet.exe if not exists -if (!(Test-Path $NUGET_EXE)) { - Write-Verbose -Message "Downloading NuGet.exe..." - try { - (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) - } catch { - Throw "Could not download NuGet.exe." - } +# If dotnet is installed locally, and expected version is not set or installation matches the expected version +if ((Get-Command "dotnet" -ErrorAction SilentlyContinue) -ne $null -and ` + (!(Test-Path variable:DotNetVersion) -or $(& dotnet --version) -eq $DotNetVersion)) { + $env:DOTNET_EXE = (Get-Command "dotnet").Path } - -# Save nuget.exe path to environment to be available to child processed -$ENV:NUGET_EXE = $NUGET_EXE - -# Restore tools from NuGet? -if(-Not $SkipToolPackageRestore.IsPresent) { - Push-Location - Set-Location $TOOLS_DIR - - # Check for changes in packages.config and remove installed tools if true. - [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) - if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or - ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { - Write-Verbose -Message "Missing or changed package.config hash..." - Remove-Item * -Recurse -Exclude packages.config,nuget.exe +else { + $DotNetDirectory = "$TempDirectory\dotnet-win" + $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" + + # Download install script + $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" + md -force $TempDirectory > $null + (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) + + # Install by channel or version + if (!(Test-Path variable:DotNetVersion)) { + ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } + } else { + ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } } - - Write-Verbose -Message "Restoring tools from NuGet..." - $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" - - if ($LASTEXITCODE -ne 0) { - Throw "An error occured while restoring NuGet tools." - } - else - { - $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" - } - Write-Verbose -Message ($NuGetOutput | out-string) - Pop-Location } -# Make sure that Cake has been installed. -if (!(Test-Path $CAKE_EXE)) { - Throw "Could not find Cake.exe at $CAKE_EXE" -} +Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" -# Start Cake -Write-Host "Running build script..." -Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -platform=`"$Platform`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseSkipTests $UseMono $UseDryRun $UseExperimental $ScriptArgs" -exit $LASTEXITCODE \ No newline at end of file +ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile -- $BuildArguments } diff --git a/build.sh b/build.sh index 206a55d171..40b1c225a6 100755 --- a/build.sh +++ b/build.sh @@ -1,105 +1,72 @@ #!/usr/bin/env bash -########################################################################## -# This is the Cake bootstrapper script for Linux and OS X. -# This file was downloaded from https://github.com/cake-build/resources -# Feel free to change this file to fit your needs. -########################################################################## +echo $(bash --version 2>&1 | head -n 1) -# Define directories. -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -TOOLS_DIR=$SCRIPT_DIR/tools -NUGET_EXE=$TOOLS_DIR/nuget.exe -CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe -PACKAGES_CONFIG=$TOOLS_DIR/packages.config -PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum - -# Define md5sum or md5 depending on Linux/OSX -MD5_EXE= -if [[ "$(uname -s)" == "Darwin" ]]; then - MD5_EXE="md5 -r" -else - MD5_EXE="md5sum" -fi - -# Define default arguments. -SCRIPT="build.cake" -TARGET="Default" -CONFIGURATION="Release" -PLATFORM="Any CPU" -VERBOSITY="verbose" -DRYRUN= -SKIP_TESTS= -SHOW_VERSION=false -SCRIPT_ARGUMENTS=() - -# Parse arguments. +#CUSTOMPARAM=0 +BUILD_ARGUMENTS=() for i in "$@"; do - case $1 in - -s|--script) SCRIPT="$2"; shift ;; - -t|--target) TARGET="$2"; shift ;; - -p|--platform) PLATFORM="$2"; shift ;; - -c|--configuration) CONFIGURATION="$2"; shift ;; - --skip-tests) SKIP_TESTS="-skip-tests"; shift ;; - -v|--verbosity) VERBOSITY="$2"; shift ;; - -d|--dryrun) DRYRUN="-dryrun" ;; - --version) SHOW_VERSION=true ;; - --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;; - *) SCRIPT_ARGUMENTS+=("$1") ;; + case $(echo $1 | awk '{print tolower($0)}') in + # -custom-param) CUSTOMPARAM=1;; + *) BUILD_ARGUMENTS+=("$1") ;; esac shift done -# Make sure the tools folder exist. -if [ ! -d "$TOOLS_DIR" ]; then - mkdir "$TOOLS_DIR" -fi +set -eo pipefail +SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) -# Make sure that packages.config exist. -if [ ! -f "$TOOLS_DIR/packages.config" ]; then - echo "Downloading packages.config..." - curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages - if [ $? -ne 0 ]; then - echo "An error occured while downloading packages.config." - exit 1 - fi -fi +########################################################################### +# CONFIGURATION +########################################################################### -# Download NuGet if it does not exist. -if [ ! -f "$NUGET_EXE" ]; then - echo "Downloading NuGet..." - curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe - if [ $? -ne 0 ]; then - echo "An error occured while downloading nuget.exe." - exit 1 - fi -fi +BUILD_PROJECT_FILE="$SCRIPT_DIR/nukebuild/_build.csproj" +TEMP_DIRECTORY="$SCRIPT_DIR//.tmp" -# Restore tools from NuGet. -pushd "$TOOLS_DIR" >/dev/null -if [ ! -f $PACKAGES_CONFIG_MD5 ] || [ "$( cat $PACKAGES_CONFIG_MD5 | sed 's/\r$//' )" != "$( $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' )" ]; then - find . -type d ! -name . | xargs rm -rf -fi +DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" +DOTNET_INSTALL_URL="https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh" +DOTNET_CHANNEL="Current" -mono "$NUGET_EXE" install -ExcludeVersion -if [ $? -ne 0 ]; then - echo "Could not restore NuGet packages." - exit 1 -fi +export DOTNET_CLI_TELEMETRY_OPTOUT=1 +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 +export NUGET_XMLDOC_MODE="skip" -$MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' >| $PACKAGES_CONFIG_MD5 +########################################################################### +# EXECUTION +########################################################################### -popd >/dev/null +function FirstJsonValue { + perl -nle 'print $1 if m{"'$1'": "([^"\-]+)",?}' <<< ${@:2} +} -# Make sure that Cake has been installed. -if [ ! -f "$CAKE_EXE" ]; then - echo "Could not find Cake.exe at '$CAKE_EXE'." - exit 1 +# If global.json exists, load expected version +if [ -f "$DOTNET_GLOBAL_FILE" ]; then + DOTNET_VERSION=$(FirstJsonValue "version" $(cat "$DOTNET_GLOBAL_FILE")) + if [ "$DOTNET_VERSION" == "" ]; then + unset DOTNET_VERSION + fi fi -# Start Cake -if $SHOW_VERSION; then - exec mono "$CAKE_EXE" -version +# If dotnet is installed locally, and expected version is not set or installation matches the expected version +if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") ]]; then + export DOTNET_EXE="$(command -v dotnet)" else - exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -platform="$PLATFORM" -configuration="$CONFIGURATION" -target=$TARGET $DRYRUN $SKIP_TESTS "${SCRIPT_ARGUMENTS[@]}" + DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" + export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" + + # Download install script + DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" + mkdir -p "$TEMP_DIRECTORY" + curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" + chmod +x "$DOTNET_INSTALL_FILE" + + # Install by channel or version + if [ -z ${DOTNET_VERSION+x} ]; then + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path + else + "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path + fi fi + +echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" + +"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]} diff --git a/cake.config b/cake.config deleted file mode 100644 index 8089cd4084..0000000000 --- a/cake.config +++ /dev/null @@ -1,15 +0,0 @@ -; This is the default configuration file for Cake. -; This file was downloaded from https://github.com/cake-build/resources - -[Nuget] -Source=https://api.nuget.org/v3/index.json -UseInProcessClient=true -LoadDependencies=false - -[Paths] -Tools=./tools -Addins=./tools/Addins -Modules=./tools/Modules - -[Settings] -SkipVerification=false diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs new file mode 100644 index 0000000000..c937933493 --- /dev/null +++ b/nukebuild/Build.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using Nuke.Common; +using Nuke.Common.Git; +using Nuke.Common.ProjectModel; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.DotNet; +using Nuke.Common.Tools.MSBuild; +using Nuke.Common.Utilities; +using static Nuke.Common.EnvironmentInfo; +using static Nuke.Common.IO.FileSystemTasks; +using static Nuke.Common.IO.PathConstruction; +using static Nuke.Common.Tools.MSBuild.MSBuildTasks; +using static Nuke.Common.Tools.DotNet.DotNetTasks; +using static Nuke.Common.Tools.Xunit.XunitTasks; + + +/* + Before editing this file, install support plugin for your IDE, + running and debugging a particular target (optionally without deps) would be way easier + ReSharper/Rider - https://plugins.jetbrains.com/plugin/10803-nuke-support + VSCode - https://marketplace.visualstudio.com/items?itemName=nuke.support + + */ + +partial class Build : NukeBuild +{ + BuildParameters Parameters { get; set; } + protected override void OnBuildInitialized() + { + Parameters = new BuildParameters(this); + Information("Building version {0} of Avalonia ({1}) using version {2} of Nuke.", + Parameters.Version, + Parameters.Configuration, + typeof(NukeBuild).Assembly.GetName().Version.ToString()); + + if (Parameters.IsLocalBuild) + { + Information("Repository Name: " + Parameters.RepositoryName); + Information("Repository Branch: " + Parameters.RepositoryBranch); + } + Information("Configuration: " + Parameters.Configuration); + Information("IsLocalBuild: " + Parameters.IsLocalBuild); + Information("IsRunningOnUnix: " + Parameters.IsRunningOnUnix); + Information("IsRunningOnWindows: " + Parameters.IsRunningOnWindows); + Information("IsRunningOnAppVeyor: " + Parameters.IsRunningOnAppVeyor); + Information("IsRunnongOnAzure:" + Parameters.IsRunningOnAzure); + Information("IsPullRequest: " + Parameters.IsPullRequest); + Information("IsMainRepo: " + Parameters.IsMainRepo); + Information("IsMasterBranch: " + Parameters.IsMasterBranch); + Information("IsReleaseBranch: " + Parameters.IsReleaseBranch); + Information("IsTagged: " + Parameters.IsTagged); + Information("IsReleasable: " + Parameters.IsReleasable); + Information("IsMyGetRelease: " + Parameters.IsMyGetRelease); + Information("IsNuGetRelease: " + Parameters.IsNuGetRelease); + } + + Target Clean => _ => _.Executes(() => + { + var data = Parameters; + DeleteDirectories(data.BuildDirs); + EnsureCleanDirectories(data.BuildDirs); + EnsureCleanDirectory(data.ArtifactsDir); + EnsureCleanDirectory(data.NugetRoot); + EnsureCleanDirectory(data.ZipRoot); + EnsureCleanDirectory(data.TestResultsRoot); + }); + + + Target Compile => _ => _ + .DependsOn(Clean) + .Executes(() => + { + var data = Parameters; + if (data.IsRunningOnWindows) + MSBuild(data.MSBuildSolution, c => c + .SetConfiguration(data.Configuration) + .SetVerbosity(MSBuildVerbosity.Minimal) + .AddProperty("PackageVersion", Parameters.Version) + .AddProperty("iOSRoslynPathHackRequired", "true") + .SetToolsVersion(MSBuildToolsVersion._15_0) + .AddTargets("Restore", "Build") + ); + + else + DotNetBuild(Parameters.MSBuildSolution, c => c + .AddProperty("PackageVersion", Parameters.Version) + .SetConfiguration(Parameters.Configuration) + ); + }); + + void RunCoreTest(string project, bool coreOnly = false) + { + if(!project.EndsWith(".csproj")) + project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj"); + Information("Running tests from " + project); + var frameworks = new List(){"netcoreapp2.0"}; + foreach(var fw in frameworks) + { + if(!fw.StartsWith("netcoreapp") && coreOnly) + continue; + Information("Running for " + fw); + DotNetTest(c => + { + c = c + .SetProjectFile(project) + .SetConfiguration(Parameters.Configuration) + .SetFramework(fw) + .EnableNoBuild() + .EnableNoRestore(); + // NOTE: I can see that we could maybe add another extension method "Switch" or "If" to make this more convenient + if (Parameters.PublishTestResults) + c = c.SetLogger("trx").SetResultsDirectory(Parameters.TestResultsRoot); + return c; + }); + } + } + + Target RunCoreLibsTests => _ => _ + .OnlyWhen(() => !Parameters.SkipTests) + .DependsOn(Compile) + .Executes(() => + { + + RunCoreTest("./tests/Avalonia.Base.UnitTests", false); + RunCoreTest("./tests/Avalonia.Controls.UnitTests", false); + RunCoreTest("./tests/Avalonia.Input.UnitTests", false); + RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", false); + RunCoreTest("./tests/Avalonia.Layout.UnitTests", false); + RunCoreTest("./tests/Avalonia.Markup.UnitTests", false); + RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", false); + RunCoreTest("./tests/Avalonia.Styling.UnitTests", false); + RunCoreTest("./tests/Avalonia.Visuals.UnitTests", false); + RunCoreTest("./tests/Avalonia.Skia.UnitTests", false); + RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", false); + + }); + + Target RunRenderTests => _ => _ + .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .DependsOn(Compile) + .Executes(() => + { + RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", true); + RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", true); + }); + + Target RunDesignerTests => _ => _ + .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .DependsOn(Compile) + .Executes(() => + { + RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", false); + }); + + [PackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe")] readonly Tool DotMemoryUnit; + + Target RunLeakTests => _ => _ + .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .DependsOn(Compile) + .Executes(() => + { + + var dotMemoryUnitPath = + ToolPathResolver.GetPackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe"); + var xunitRunnerPath = + ToolPathResolver.GetPackageExecutable("xunit.runner.console", "xunit.console.x86.exe"); + var args = new[] + { + Path.GetFullPath(xunitRunnerPath), + "--propagate-exit-code", + "--", + "tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll" + }; + var cargs = string.Join(" ", args.Select(a => '"' + a + '"')); + + var proc = Process.Start(new ProcessStartInfo(dotMemoryUnitPath, cargs) + { + UseShellExecute = false + }); + + if (!proc.WaitForExit(120000)) + { + proc.Kill(); + throw new Exception("Leak tests timed out"); + } + + var leakTestsExitCode = proc.ExitCode; + + if (leakTestsExitCode != 0) + { + throw new Exception("Leak Tests failed"); + } + + + var testAssembly = "tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll"; + DotMemoryUnit( + $"{XunitPath.DoubleQuoteIfNeeded()} --propagate-exit-code -- {testAssembly}", + timeout: 120_000); + }); + + Target ZipFiles => _ => _ + .After(CreateNugetPackages, Compile, RunCoreLibsTests, Package) + .Executes(() => + { + var data = Parameters; + Zip(data.ZipCoreArtifacts, data.BinRoot); + + Zip(data.ZipNuGetArtifacts, data.NugetRoot); + + Zip(data.ZipTargetControlCatalogDesktopDir, + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dll").Concat( + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.config")).Concat( + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.so")).Concat( + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dylib")).Concat( + GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.exe"))); + }); + + Target CreateNugetPackages => _ => _ + .DependsOn(Compile) + .After(RunTests) + .Executes(() => + { + if (Parameters.IsRunningOnWindows) + + MSBuild(Parameters.MSBuildSolution, c => c + .SetConfiguration(Parameters.Configuration) + .SetVerbosity(MSBuildVerbosity.Minimal) + .AddProperty("PackageVersion", Parameters.Version) + .AddProperty("iOSRoslynPathHackRequired", "true") + .SetToolsVersion(MSBuildToolsVersion._15_0) + .AddTargets("Restore", "Pack")); + else + DotNetPack(Parameters.MSBuildSolution, c => + c.SetConfiguration(Parameters.Configuration) + .AddProperty("PackageVersion", Parameters.Version)); + }); + + Target RunTests => _ => _ + .DependsOn(RunCoreLibsTests) + .DependsOn(RunRenderTests) + .DependsOn(RunDesignerTests) + .DependsOn(RunLeakTests); + + Target Package => _ => _ + .DependsOn(RunTests) + .DependsOn(CreateNugetPackages); + + Target CiAppVeyor => _ => _ + .DependsOn(Package) + .DependsOn(ZipFiles); + + Target CiTravis => _ => _ + .DependsOn(RunTests); + + Target CiAsuzeLinux => _ => _ + .DependsOn(RunTests); + + Target CiAsuzeOSX => _ => _ + .DependsOn(Package) + .DependsOn(ZipFiles); + + Target CiAsuzeWindows => _ => _ + .DependsOn(Package) + .DependsOn(ZipFiles); + + + public static int Main() => + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Execute(x => x.Package) + : Execute(x => x.RunTests); + +} diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs new file mode 100644 index 0000000000..322871e5db --- /dev/null +++ b/nukebuild/BuildParameters.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using Nuke.Common; +using Nuke.Common.BuildServers; +using Nuke.Common.Execution; +using Nuke.Common.IO; +using static Nuke.Common.IO.FileSystemTasks; +using static Nuke.Common.IO.PathConstruction; +using static Nuke.Common.Tools.MSBuild.MSBuildTasks; + +public partial class Build +{ + [Parameter("configuration")] + public string NukeArgConfiguration { get; set; } + + [Parameter("skip-tests")] + public bool NukeArgSkipTests { get; set; } + + [Parameter("force-nuget-version")] + public string NukeArgForceNugetVersion { get; set; } + + public class BuildParameters + { + public string Configuration { get; } + public bool SkipTests { get; } + public string MainRepo { get; } + public string MasterBranch { get; } + public string RepositoryName { get; } + public string RepositoryBranch { get; } + public string ReleaseConfiguration { get; } + public string ReleaseBranchPrefix { get; } + public string MSBuildSolution { get; } + public bool IsLocalBuild { get; } + public bool IsRunningOnUnix { get; } + public bool IsRunningOnWindows { get; } + public bool IsRunningOnAppVeyor { get; } + public bool IsRunningOnAzure { get; } + public bool IsPullRequest { get; } + public bool IsMainRepo { get; } + public bool IsMasterBranch { get; } + public bool IsReleaseBranch { get; } + public bool IsTagged { get; } + public bool IsReleasable { get; } + public bool IsMyGetRelease { get; } + public bool IsNuGetRelease { get; } + public bool PublishTestResults { get; } + public string Version { get; } + public AbsolutePath ArtifactsDir { get; } + public AbsolutePath NugetRoot { get; } + public AbsolutePath ZipRoot { get; } + public AbsolutePath BinRoot { get; } + public AbsolutePath TestResultsRoot { get; } + public string DirSuffix { get; } + public List BuildDirs { get; } + public string FileZipSuffix { get; } + public AbsolutePath ZipCoreArtifacts { get; } + public AbsolutePath ZipNuGetArtifacts { get; } + public AbsolutePath ZipSourceControlCatalogDesktopDir { get; } + public AbsolutePath ZipTargetControlCatalogDesktopDir { get; } + + + public BuildParameters(Build b) + { + var buildSystem = Host; + + + // ARGUMENTS + Configuration = b.NukeArgConfiguration ?? "Release"; + SkipTests = b.NukeArgSkipTests; + + // CONFIGURATION + MainRepo = "https://github.com/AvaloniaUI/Avalonia"; + MasterBranch = "refs/heads/master"; + ReleaseBranchPrefix = "refs/heads/release/"; + ReleaseConfiguration = "Release"; + MSBuildSolution = RootDirectory / "dirs.proj"; + + // PARAMETERS + IsLocalBuild = buildSystem == HostType.Console; + IsRunningOnUnix = Environment.OSVersion.Platform == PlatformID.Unix || + Environment.OSVersion.Platform == PlatformID.MacOSX; + IsRunningOnWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + IsRunningOnAppVeyor = buildSystem == HostType.AppVeyor; + IsRunningOnAzure = buildSystem == HostType.TeamServices || + Environment.GetEnvironmentVariable("LOGNAME") == "vsts"; + + string tagName = null; + if (IsRunningOnAppVeyor) + { + IsPullRequest = AppVeyor.Instance.PullRequestNumber != 0; + RepositoryName = Environment.GetEnvironmentVariable("BUILD_REPOSITORY_URI"); + RepositoryBranch = Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCH"); + + IsReleaseBranch = + (Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCH") ?? "").StartsWith(ReleaseBranchPrefix, + StringComparison.OrdinalIgnoreCase); + IsTagged = AppVeyor.Instance.RepositoryTag + && !string.IsNullOrWhiteSpace(AppVeyor.Instance.RepositoryTagName); + + tagName = AppVeyor.Instance.RepositoryTagName; + } + else if (IsRunningOnAzure) + { + RepositoryName = TeamServices.Instance.RepositoryUri; + RepositoryBranch = TeamServices.Instance.SourceBranch; + IsPullRequest = TeamServices.Instance.PullRequestId.HasValue; + IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, TeamServices.Instance.RepositoryUri); + + // TODO??? + IsTagged = false; + tagName = null; + } + IsMainRepo = + StringComparer.OrdinalIgnoreCase.Equals(MainRepo, + RepositoryName); + IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, + RepositoryBranch); + + + IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration); + IsMyGetRelease = !IsTagged && IsReleasable; + IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch; + + // VERSION + Version = b.NukeArgForceNugetVersion ?? GetVersion(); + + if (IsRunningOnAppVeyor) + { + string tagVersion = null; + if (IsTagged) + { + var tag = tagName; + var nugetReleasePrefix = "nuget-release-"; + IsNuGetRelease = IsTagged && IsReleasable && tag.StartsWith(nugetReleasePrefix); + if (IsNuGetRelease) + tagVersion = tag.Substring(nugetReleasePrefix.Length); + } + + if (tagVersion != null) + { + Version = tagVersion; + } + else + { + // Use AssemblyVersion with Build as version + Version += "-build" + Environment.GetEnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta"; + } + } + else if (IsRunningOnAzure) + { + if (!IsNuGetRelease) + { + // Use AssemblyVersion with Build as version + Version += "-build" + Environment.GetEnvironmentVariable("BUILD_BUILDID") + "-beta"; + } + + PublishTestResults = true; + } + + // DIRECTORIES + ArtifactsDir = RootDirectory / "artifacts"; + NugetRoot = ArtifactsDir / "nuget"; + ZipRoot = ArtifactsDir / "zip"; + BinRoot = ArtifactsDir / "bin"; + TestResultsRoot = ArtifactsDir / "test-results"; + BuildDirs = GlobDirectories(RootDirectory, "**bin").Concat(GlobDirectories(RootDirectory, "**obj")).ToList(); + DirSuffix = Configuration; + FileZipSuffix = Version + ".zip"; + ZipCoreArtifacts = ZipRoot / ("Avalonia-" + FileZipSuffix); + ZipNuGetArtifacts = ZipRoot / ("Avalonia-NuGet-" + FileZipSuffix); + ZipSourceControlCatalogDesktopDir = + RootDirectory / ("samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461"); + ZipTargetControlCatalogDesktopDir = ZipRoot / ("ControlCatalog.Desktop-" + FileZipSuffix); + } + + private static string GetVersion() + { + var xdoc = XDocument.Load("./build/SharedVersion.props"); + return xdoc.Descendants().First(x => x.Name.LocalName == "Version").Value; + } + } + +} diff --git a/nukebuild/Shims.cs b/nukebuild/Shims.cs new file mode 100644 index 0000000000..7f26490493 --- /dev/null +++ b/nukebuild/Shims.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using Nuke.Common; +using Nuke.Common.IO; + +public partial class Build +{ + static void Information(string info) + { + Logger.Info(info); + } + + static void Information(string info, params object[] args) + { + Logger.Info(info, args); + } + + private void Zip(PathConstruction.AbsolutePath target, params string[] paths) => Zip(target, paths.AsEnumerable()); + + private void Zip(PathConstruction.AbsolutePath target, IEnumerable paths) + { + var targetPath = target.ToString(); + bool finished = false, atLeastOneFileAdded = false; + try + { + using (var targetStream = File.Create(targetPath)) + using(var archive = new System.IO.Compression.ZipArchive(targetStream, ZipArchiveMode.Create)) + { + void AddFile(string path, string relativePath) + { + var e = archive.CreateEntry(relativePath.Replace("\\", "/"), CompressionLevel.Optimal); + using (var entryStream = e.Open()) + using (var fileStream = File.OpenRead(path)) + fileStream.CopyTo(entryStream); + atLeastOneFileAdded = true; + } + + foreach (var path in paths) + { + + if (Directory.Exists(path)) + { + var dirInfo = new DirectoryInfo(path); + var rootPath = Path.GetDirectoryName(dirInfo.FullName); + foreach(var fsEntry in dirInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) + { + if (fsEntry is FileInfo) + { + var relPath = Path.GetRelativePath(rootPath, fsEntry.FullName); + AddFile(fsEntry.FullName, relPath); + } + } + } + else if(File.Exists(path)) + { + var name = Path.GetFileName(path); + AddFile(path, name); + } + } + } + + finished = true; + } + finally + { + try + { + if (!finished || !atLeastOneFileAdded) + File.Delete(targetPath); + } + catch + { + //Ignore + } + } + } +} diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj new file mode 100644 index 0000000000..ed94116a93 --- /dev/null +++ b/nukebuild/_build.csproj @@ -0,0 +1,35 @@ + + + + Exe + netcoreapp2.0 + false + + False + CS0649;CS0169 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nukebuild/_build.csproj.DotSettings b/nukebuild/_build.csproj.DotSettings new file mode 100644 index 0000000000..9aac7d8e8d --- /dev/null +++ b/nukebuild/_build.csproj.DotSettings @@ -0,0 +1,24 @@ + + False + Implicit + Implicit + ExpressionBody + 0 + NEXT_LINE + True + False + 120 + IF_OWNER_IS_SINGLE_LINE + WRAP_IF_LONG + False + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True From 07475d12f9d973c307de4584e291def4ec009d4a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 14:56:07 +0000 Subject: [PATCH 088/147] simplify apply styles --- src/Windows/Avalonia.Win32/WindowImpl.cs | 69 ++++++++++++------------ 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 8343ccbd44..4316166343 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -271,7 +271,9 @@ namespace Avalonia.Win32 return; } - UpdateWMStyles(() => _decorated = value); + _decorated = value; + + UpdateWMStyles(); } public void Invalidate(Rect rect) @@ -884,15 +886,15 @@ namespace Avalonia.Win32 } } - private void UpdateWMStyles(Action changer) + private void UpdateWMStyles() { - var decorated = _decorated; + var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); - var resizable = _resizable; + const WindowStyles controlledFlags = WindowStyles.WS_OVERLAPPEDWINDOW; - changer(); + style = style | controlledFlags ^ controlledFlags; - var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE) | WindowStyles.WS_OVERLAPPEDWINDOW; + style |= WindowStyles.WS_OVERLAPPEDWINDOW; if (!_decorated) { @@ -904,40 +906,39 @@ namespace Avalonia.Win32 style ^= (WindowStyles.WS_SIZEFRAME); } + UnmanagedMethods.GetWindowRect(_hwnd, out var oldRect); + SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); - if (decorated != _decorated) - { - UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - var oldThickness = BorderThickness; + var oldThickness = BorderThickness; - Rect newRect; + Rect newRect; - if (_decorated) - { - var thickness = BorderThickness; + if (_decorated) + { + var thickness = BorderThickness; - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + { + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + } - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, + (int)newRect.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); - } } public void CanResize(bool value) @@ -947,7 +948,9 @@ namespace Avalonia.Win32 return; } - UpdateWMStyles(() => _resizable = value); + _resizable = value; + + UpdateWMStyles(); } public void SetTopmost(bool value) From ca59da108ed2a35b1b49e1ab1c1a051bf31db100 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:08:29 +0000 Subject: [PATCH 089/147] [win32] smoothly toggle decorations. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4316166343..1383a81dd6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -906,14 +906,12 @@ namespace Avalonia.Win32 style ^= (WindowStyles.WS_SIZEFRAME); } - UnmanagedMethods.GetWindowRect(_hwnd, out var oldRect); + var oldThickness = BorderThickness; SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - var oldThickness = BorderThickness; - Rect newRect; if (_decorated) From 333c0ac907d57bda332600283e6ab724d465b346 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:13:20 +0000 Subject: [PATCH 090/147] simply applywm styles. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1383a81dd6..146ab496fa 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -894,16 +894,20 @@ namespace Avalonia.Win32 style = style | controlledFlags ^ controlledFlags; - style |= WindowStyles.WS_OVERLAPPEDWINDOW; + style |= WindowStyles.WS_OVERLAPPED | + WindowStyles.WS_MINIMIZEBOX | + WindowStyles.WS_MAXIMIZEBOX; - if (!_decorated) + if (_decorated) { - style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU); + style |= + WindowStyles.WS_CAPTION | + WindowStyles.WS_SYSMENU; } - if (!_resizable) + if (_resizable) { - style ^= (WindowStyles.WS_SIZEFRAME); + style |= WindowStyles.WS_SIZEFRAME; } var oldThickness = BorderThickness; @@ -936,7 +940,6 @@ namespace Avalonia.Win32 UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, (int)newRect.Height, UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); - } public void CanResize(bool value) From 99a1392febc429eaae2dbc6234ee4893fb375de1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:16:04 +0000 Subject: [PATCH 091/147] remove flags from overlapped. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 146ab496fa..1383a81dd6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -894,20 +894,16 @@ namespace Avalonia.Win32 style = style | controlledFlags ^ controlledFlags; - style |= WindowStyles.WS_OVERLAPPED | - WindowStyles.WS_MINIMIZEBOX | - WindowStyles.WS_MAXIMIZEBOX; + style |= WindowStyles.WS_OVERLAPPEDWINDOW; - if (_decorated) + if (!_decorated) { - style |= - WindowStyles.WS_CAPTION | - WindowStyles.WS_SYSMENU; + style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU); } - if (_resizable) + if (!_resizable) { - style |= WindowStyles.WS_SIZEFRAME; + style ^= (WindowStyles.WS_SIZEFRAME); } var oldThickness = BorderThickness; @@ -940,6 +936,7 @@ namespace Avalonia.Win32 UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, (int)newRect.Height, UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + } public void CanResize(bool value) From 7d1f145f9331ba3b0cc47a3963b4324e20d38f3b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:34:19 +0000 Subject: [PATCH 092/147] add CanResize checkbox to decorated window. --- samples/ControlCatalog/DecoratedWindow.xaml | 63 ++++++++++---------- src/Windows/Avalonia.Win32/WindowImpl.cs | 64 +++++++++++---------- 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index 9f05cd1f57..b2462cda26 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -3,36 +3,37 @@ x:Class="ControlCatalog.DecoratedWindow" Title="Avalonia Control Gallery" xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> - - - - Title - - - - - - - - - - - - Hello world! + + + + Title + + + + + + + + + + + + Hello world! - Decorated - - - - - - - - - - - - + Decorated + CanResize + + + + + + + + + + + + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1383a81dd6..22dd6142fc 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -271,9 +271,7 @@ namespace Avalonia.Win32 return; } - _decorated = value; - - UpdateWMStyles(); + UpdateWMStyles(() => _decorated = value); } public void Invalidate(Rect rect) @@ -850,7 +848,8 @@ namespace Avalonia.Win32 private static int ToInt32(IntPtr ptr) { - if (IntPtr.Size == 4) return ptr.ToInt32(); + if (IntPtr.Size == 4) + return ptr.ToInt32(); return (int)(ptr.ToInt64() & 0xffffffff); } @@ -886,8 +885,13 @@ namespace Avalonia.Win32 } } - private void UpdateWMStyles() + private void UpdateWMStyles(Action change) { + var decorated = _decorated; + var resizable = _resizable; + + change(); + var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); const WindowStyles controlledFlags = WindowStyles.WS_OVERLAPPEDWINDOW; @@ -910,33 +914,35 @@ namespace Avalonia.Win32 SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); - UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - - Rect newRect; - - if (_decorated) + if (decorated != _decorated) { var thickness = BorderThickness; - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + Rect newRect; + if (_decorated) + { + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + { + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + } + + UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, + (int)newRect.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + } } public void CanResize(bool value) @@ -946,9 +952,7 @@ namespace Avalonia.Win32 return; } - _resizable = value; - - UpdateWMStyles(); + UpdateWMStyles(() => _resizable = value); } public void SetTopmost(bool value) From 5d327abc65ab65da15e45febbfbe4583cd7404d0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:34:19 +0000 Subject: [PATCH 093/147] Revert "add CanResize checkbox to decorated window." This reverts commit 7d1f145f9331ba3b0cc47a3963b4324e20d38f3b. --- samples/ControlCatalog/DecoratedWindow.xaml | 63 ++++++++++---------- src/Windows/Avalonia.Win32/WindowImpl.cs | 64 ++++++++++----------- 2 files changed, 61 insertions(+), 66 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index b2462cda26..9f05cd1f57 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -3,37 +3,36 @@ x:Class="ControlCatalog.DecoratedWindow" Title="Avalonia Control Gallery" xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> - - - - Title - - - - - - - - - - - - Hello world! + + + + Title + + + + + + + + + + + + Hello world! - Decorated - CanResize - - - - - - - - - - - - + Decorated + + + + + + + + + + + + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 22dd6142fc..1383a81dd6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -271,7 +271,9 @@ namespace Avalonia.Win32 return; } - UpdateWMStyles(() => _decorated = value); + _decorated = value; + + UpdateWMStyles(); } public void Invalidate(Rect rect) @@ -848,8 +850,7 @@ namespace Avalonia.Win32 private static int ToInt32(IntPtr ptr) { - if (IntPtr.Size == 4) - return ptr.ToInt32(); + if (IntPtr.Size == 4) return ptr.ToInt32(); return (int)(ptr.ToInt64() & 0xffffffff); } @@ -885,13 +886,8 @@ namespace Avalonia.Win32 } } - private void UpdateWMStyles(Action change) + private void UpdateWMStyles() { - var decorated = _decorated; - var resizable = _resizable; - - change(); - var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); const WindowStyles controlledFlags = WindowStyles.WS_OVERLAPPEDWINDOW; @@ -914,35 +910,33 @@ namespace Avalonia.Win32 SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); - if (decorated != _decorated) + UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); + + Rect newRect; + + if (_decorated) { var thickness = BorderThickness; - UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - - Rect newRect; + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + { + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + } - if (_decorated) - { - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } + UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, + (int)newRect.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); - } } public void CanResize(bool value) @@ -952,7 +946,9 @@ namespace Avalonia.Win32 return; } - UpdateWMStyles(() => _resizable = value); + _resizable = value; + + UpdateWMStyles(); } public void SetTopmost(bool value) From 3ce39fe7a07eac3d111a7b261a4cde1b1400faaa Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:42:56 +0000 Subject: [PATCH 094/147] use Action to set flags and recalculate wmstyles. --- samples/ControlCatalog/DecoratedWindow.xaml | 64 +++++++++++---------- src/Windows/Avalonia.Win32/WindowImpl.cs | 59 ++++++++++--------- 2 files changed, 64 insertions(+), 59 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index 9f05cd1f57..cb6016b324 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -3,36 +3,38 @@ x:Class="ControlCatalog.DecoratedWindow" Title="Avalonia Control Gallery" xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> - - - - Title - - - - - - - - - - - - Hello world! + + + + Title + + + + + + + + + + + + Hello world! - Decorated - - - - - - - - - - - - + Decorated + + CanResize + + + + + + + + + + + + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1383a81dd6..7a1802826d 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -271,9 +271,7 @@ namespace Avalonia.Win32 return; } - _decorated = value; - - UpdateWMStyles(); + UpdateWMStyles(()=> _decorated = value); } public void Invalidate(Rect rect) @@ -886,8 +884,13 @@ namespace Avalonia.Win32 } } - private void UpdateWMStyles() + private void UpdateWMStyles(Action change) { + var decorated = _decorated; + var resizable = _resizable; + + change(); + var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); const WindowStyles controlledFlags = WindowStyles.WS_OVERLAPPEDWINDOW; @@ -912,31 +915,33 @@ namespace Avalonia.Win32 UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - Rect newRect; - - if (_decorated) + if (decorated != _decorated) { - var thickness = BorderThickness; + Rect newRect; - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } + if (_decorated) + { + var thickness = BorderThickness; - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + { + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + } + UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, + (int)newRect.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + } } public void CanResize(bool value) @@ -946,9 +951,7 @@ namespace Avalonia.Win32 return; } - _resizable = value; - - UpdateWMStyles(); + UpdateWMStyles(()=> _resizable = value); } public void SetTopmost(bool value) From b91233e3dfc0c706042cbe6c5470ae2a5711d0df Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:47:37 +0000 Subject: [PATCH 095/147] remove unused var --- src/Windows/Avalonia.Win32/WindowImpl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 7a1802826d..5b5aeb88ab 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -887,7 +887,6 @@ namespace Avalonia.Win32 private void UpdateWMStyles(Action change) { var decorated = _decorated; - var resizable = _resizable; change(); From f0f7cf44272866ea7adf5962f7bd8caa527d8dcf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 15:53:50 +0000 Subject: [PATCH 096/147] [win32 window] fix BorderThickness --- src/Windows/Avalonia.Win32/WindowImpl.cs | 32 +++++++++++++----------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5b5aeb88ab..3eeccb368c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -95,18 +95,25 @@ namespace Avalonia.Win32 { get { - var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); + if (_decorated) + { + var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); - var padding = new RECT(); + var padding = new RECT(); - if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle)) - { - return new Thickness(-padding.left, -padding.top, padding.right, padding.bottom); + if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle)) + { + return new Thickness(-padding.left, -padding.top, padding.right, padding.bottom); + } + else + { + throw new Win32Exception(); + } } else { - throw new Win32Exception(); + return new Thickness(); } } } @@ -149,12 +156,7 @@ namespace Avalonia.Win32 if (value != ClientSize) { value *= Scaling; - - if (_decorated) - { - value += BorderThickness; - } - + UnmanagedMethods.SetWindowPos( _hwnd, IntPtr.Zero, @@ -888,6 +890,8 @@ namespace Avalonia.Win32 { var decorated = _decorated; + var oldThickness = BorderThickness; + change(); var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE); @@ -908,8 +912,6 @@ namespace Avalonia.Win32 style ^= (WindowStyles.WS_SIZEFRAME); } - var oldThickness = BorderThickness; - SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); From a54b85bfa7507e9124f06e31bf1cfe8c00574233 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 1 Dec 2018 20:17:06 +0300 Subject: [PATCH 097/147] A bit more clean method of new window rect calculation --- .../Interop/UnmanagedMethods.cs | 10 ++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 48 +++++++++---------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index adfbf0cb52..60b56ea580 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1172,6 +1172,8 @@ namespace Avalonia.Win32.Interop public int right; public int bottom; + public int Width => right - left; + public int Height => bottom - top; public RECT(Rect rect) { left = (int)rect.X; @@ -1179,6 +1181,14 @@ namespace Avalonia.Win32.Interop right = (int)(rect.X + rect.Width); bottom = (int)(rect.Y + rect.Height); } + + public void Offset(POINT pt) + { + left += pt.X; + right += pt.X; + top += pt.Y; + bottom += pt.Y; + } } [StructLayout(LayoutKind.Sequential)] diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 3eeccb368c..1213be1bf0 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -888,7 +888,7 @@ namespace Avalonia.Win32 private void UpdateWMStyles(Action change) { - var decorated = _decorated; + var oldDecorated = _decorated; var oldThickness = BorderThickness; @@ -912,37 +912,33 @@ namespace Avalonia.Win32 style ^= (WindowStyles.WS_SIZEFRAME); } + GetClientRect(_hwnd, out var oldClientRect); + var oldClientRectOrigin = new UnmanagedMethods.POINT(); + ClientToScreen(_hwnd, ref oldClientRectOrigin); + oldClientRect.Offset(oldClientRectOrigin); + + SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style); UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect); - - if (decorated != _decorated) + bool frameUpdated = false; + if (oldDecorated != _decorated) { - Rect newRect; - + var newRect = oldClientRect; if (_decorated) - { - var thickness = BorderThickness; - - newRect = new Rect( - windowRect.left - thickness.Left, - windowRect.top - thickness.Top, - (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), - (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); - } - else - { - newRect = new Rect( - windowRect.left + oldThickness.Left, - windowRect.top + oldThickness.Top, - (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), - (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); - } - - UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width, - (int)newRect.Height, - UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + AdjustWindowRectEx(ref newRect, (uint)style, false, + GetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE)); + _changingDecorations = true; + SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height, + SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); + frameUpdated = true; } + + if (!frameUpdated) + SetWindowPos(_hwnd, IntPtr.Zero, 0, 0, 0, 0, + SetWindowPosFlags.SWP_FRAMECHANGED | SetWindowPosFlags.SWP_NOZORDER | + SetWindowPosFlags.SWP_NOACTIVATE + | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE); } public void CanResize(bool value) From 6254036fb55a89bfc4dac0f8491c194e7cf0c2e0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 1 Dec 2018 20:18:21 +0300 Subject: [PATCH 098/147] Opps --- src/Windows/Avalonia.Win32/WindowImpl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1213be1bf0..4f97f9a472 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -928,7 +928,6 @@ namespace Avalonia.Win32 if (_decorated) AdjustWindowRectEx(ref newRect, (uint)style, false, GetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE)); - _changingDecorations = true; SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height, SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); frameUpdated = true; From 422d798108339e7c8a13f90e261654fa318278c2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Dec 2018 20:56:10 +0000 Subject: [PATCH 099/147] fix non-client WM_Activate. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4f97f9a472..0b9d16daa0 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -606,13 +606,19 @@ namespace Avalonia.Win32 break; case WindowsMessage.WM_NCPAINT: - case WindowsMessage.WM_NCACTIVATE: if (!_decorated) { return IntPtr.Zero; } break; + case WindowsMessage.WM_NCACTIVATE: + if (!_decorated) + { + return new IntPtr(1); + } + break; + case UnmanagedMethods.WindowsMessage.WM_PAINT: UnmanagedMethods.PAINTSTRUCT ps; if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero) From 3c5ac8f0cc862694fc9932cc3a5f05fca9ad9da6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 2 Dec 2018 00:45:51 +0000 Subject: [PATCH 100/147] allow menu borders to be overridden. --- src/Avalonia.Themes.Default/MenuItem.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index 07faf7a632..f2940227e1 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -49,7 +49,7 @@ ObeyScreenEdges="True"> + BorderThickness="{TemplateBinding BorderThickness}"> + BorderThickness="{TemplateBinding BorderThickness}"> Date: Sun, 2 Dec 2018 10:51:35 +0300 Subject: [PATCH 101/147] Fixed review comments --- .travis.yml | 24 ---------- appveyor.yml | 23 --------- azure-pipelines.yml | 6 +-- nukebuild/Build.cs | 90 +++++++++++------------------------- nukebuild/BuildParameters.cs | 60 +++--------------------- nukebuild/Shims.cs | 1 - 6 files changed, 36 insertions(+), 168 deletions(-) delete mode 100644 .travis.yml delete mode 100644 appveyor.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d60323b418..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: csharp -os: - - linux -dist: trusty -osx_image: xcode8.3 -env: - global: - - DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 - - DOTNET_CLI_TELEMETRY_OPTOUT=1 -mono: - - 5.2.0 -dotnet: 2.1.200 -script: - - sudo apt-get update - - sudo apt-get install castxml - - ./build.sh --target "CiTravis" --configuration "Release" -notifications: - email: false - webhooks: - urls: - - https://webhooks.gitter.im/e/98f653320ef2b7506c05 - on_success: change - on_failure: always - on_start: never diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 8694495e66..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,23 +0,0 @@ -os: Visual Studio 2017 -skip_branch_with_pr: true -configuration: -- Release -environment: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - NUGET_API_URL: https://www.nuget.org/api/v2/package - MYGET_API_KEY: - secure: OtVfyN3ErqQrDTnWH2HDfJDlCiu/i4/X4wFmK3ZXXP7HmCiXYPSbTjMPwwdOxRaK - MYGET_API_URL: https://www.myget.org/F/avalonia-ci/api/v2/package -init: -- ps: if (Test-Path env:nuget_address) {[System.IO.File]::AppendAllText("C:\Windows\System32\drivers\etc\hosts", "`n$($env:nuget_address)`tapi.nuget.org")} -before_build: -- git submodule update --init -build_script: -- ps: .\build.ps1 --target "CiAppVeyor" --configuration "$env:configuration" - -test: off -artifacts: - - path: artifacts\nuget\*.nupkg - - path: artifacts\zip\*.zip - - path: artifacts\inspectcode.xml diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dec361affe..8c5380e65e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,7 +22,7 @@ jobs: export PATH="$PATH:$HOME/.dotnet/tools" dotnet --info printenv - nuke --target="CiAzureLinux" --configuration="Release" + nuke --target CiAzureLinux --configuration=Release - task: PublishTestResults@2 inputs: @@ -71,7 +71,7 @@ jobs: export PATH="$PATH:$HOME/.dotnet/tools" dotnet --info printenv - nuke --target="CiAzureOSX" --configuration="Release" + nuke --target CiAzureOSX --configuration Release - task: PublishTestResults@2 inputs: @@ -106,7 +106,7 @@ jobs: inputs: script: | set PATH=%PATH%;%USERPROFILE%\.dotnet\tools - nuke --target="CiAzureWindows" --configuration="Release" + nuke --target CiAzureWindows --configuration Release - task: PublishTestResults@2 inputs: diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index c937933493..63e889ae30 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -48,13 +48,11 @@ partial class Build : NukeBuild Information("IsLocalBuild: " + Parameters.IsLocalBuild); Information("IsRunningOnUnix: " + Parameters.IsRunningOnUnix); Information("IsRunningOnWindows: " + Parameters.IsRunningOnWindows); - Information("IsRunningOnAppVeyor: " + Parameters.IsRunningOnAppVeyor); - Information("IsRunnongOnAzure:" + Parameters.IsRunningOnAzure); + Information("IsRunningOnAzure:" + Parameters.IsRunningOnAzure); Information("IsPullRequest: " + Parameters.IsPullRequest); Information("IsMainRepo: " + Parameters.IsMainRepo); Information("IsMasterBranch: " + Parameters.IsMasterBranch); Information("IsReleaseBranch: " + Parameters.IsReleaseBranch); - Information("IsTagged: " + Parameters.IsTagged); Information("IsReleasable: " + Parameters.IsReleasable); Information("IsMyGetRelease: " + Parameters.IsMyGetRelease); Information("IsNuGetRelease: " + Parameters.IsNuGetRelease); @@ -62,29 +60,37 @@ partial class Build : NukeBuild Target Clean => _ => _.Executes(() => { - var data = Parameters; - DeleteDirectories(data.BuildDirs); - EnsureCleanDirectories(data.BuildDirs); - EnsureCleanDirectory(data.ArtifactsDir); - EnsureCleanDirectory(data.NugetRoot); - EnsureCleanDirectory(data.ZipRoot); - EnsureCleanDirectory(data.TestResultsRoot); + DeleteDirectories(Parameters.BuildDirs); + EnsureCleanDirectories(Parameters.BuildDirs); + EnsureCleanDirectory(Parameters.ArtifactsDir); + EnsureCleanDirectory(Parameters.NugetRoot); + EnsureCleanDirectory(Parameters.ZipRoot); + EnsureCleanDirectory(Parameters.TestResultsRoot); }); - + [Serializable] + class MsBuildSettingsWithRestore : MSBuildSettings + { + protected override Arguments ConfigureArguments(Arguments arguments) + { + arguments.Add("/restore"); + return base.ConfigureArguments(arguments); + } + } + Target Compile => _ => _ .DependsOn(Clean) .Executes(() => { - var data = Parameters; - if (data.IsRunningOnWindows) - MSBuild(data.MSBuildSolution, c => c - .SetConfiguration(data.Configuration) + + if (Parameters.IsRunningOnWindows) + MSBuild(Parameters.MSBuildSolution, c => new MsBuildSettingsWithRestore() + .SetConfiguration(Parameters.Configuration) .SetVerbosity(MSBuildVerbosity.Minimal) .AddProperty("PackageVersion", Parameters.Version) .AddProperty("iOSRoslynPathHackRequired", "true") .SetToolsVersion(MSBuildToolsVersion._15_0) - .AddTargets("Restore", "Build") + .AddTargets("Build") ); else @@ -126,7 +132,6 @@ partial class Build : NukeBuild .DependsOn(Compile) .Executes(() => { - RunCoreTest("./tests/Avalonia.Base.UnitTests", false); RunCoreTest("./tests/Avalonia.Controls.UnitTests", false); RunCoreTest("./tests/Avalonia.Input.UnitTests", false); @@ -138,7 +143,6 @@ partial class Build : NukeBuild RunCoreTest("./tests/Avalonia.Visuals.UnitTests", false); RunCoreTest("./tests/Avalonia.Skia.UnitTests", false); RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", false); - }); Target RunRenderTests => _ => _ @@ -165,39 +169,6 @@ partial class Build : NukeBuild .DependsOn(Compile) .Executes(() => { - - var dotMemoryUnitPath = - ToolPathResolver.GetPackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe"); - var xunitRunnerPath = - ToolPathResolver.GetPackageExecutable("xunit.runner.console", "xunit.console.x86.exe"); - var args = new[] - { - Path.GetFullPath(xunitRunnerPath), - "--propagate-exit-code", - "--", - "tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll" - }; - var cargs = string.Join(" ", args.Select(a => '"' + a + '"')); - - var proc = Process.Start(new ProcessStartInfo(dotMemoryUnitPath, cargs) - { - UseShellExecute = false - }); - - if (!proc.WaitForExit(120000)) - { - proc.Kill(); - throw new Exception("Leak tests timed out"); - } - - var leakTestsExitCode = proc.ExitCode; - - if (leakTestsExitCode != 0) - { - throw new Exception("Leak Tests failed"); - } - - var testAssembly = "tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll"; DotMemoryUnit( $"{XunitPath.DoubleQuoteIfNeeded()} --propagate-exit-code -- {testAssembly}", @@ -210,9 +181,7 @@ partial class Build : NukeBuild { var data = Parameters; Zip(data.ZipCoreArtifacts, data.BinRoot); - Zip(data.ZipNuGetArtifacts, data.NugetRoot); - Zip(data.ZipTargetControlCatalogDesktopDir, GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dll").Concat( GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.config")).Concat( @@ -234,7 +203,7 @@ partial class Build : NukeBuild .AddProperty("PackageVersion", Parameters.Version) .AddProperty("iOSRoslynPathHackRequired", "true") .SetToolsVersion(MSBuildToolsVersion._15_0) - .AddTargets("Restore", "Pack")); + .AddTargets("Pack")); else DotNetPack(Parameters.MSBuildSolution, c => c.SetConfiguration(Parameters.Configuration) @@ -251,21 +220,14 @@ partial class Build : NukeBuild .DependsOn(RunTests) .DependsOn(CreateNugetPackages); - Target CiAppVeyor => _ => _ - .DependsOn(Package) - .DependsOn(ZipFiles); - - Target CiTravis => _ => _ - .DependsOn(RunTests); - - Target CiAsuzeLinux => _ => _ + Target CiAzureLinux => _ => _ .DependsOn(RunTests); - Target CiAsuzeOSX => _ => _ + Target CiAzureOSX => _ => _ .DependsOn(Package) .DependsOn(ZipFiles); - Target CiAsuzeWindows => _ => _ + Target CiAzureWindows => _ => _ .DependsOn(Package) .DependsOn(ZipFiles); diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs index 322871e5db..9029aef60d 100644 --- a/nukebuild/BuildParameters.cs +++ b/nukebuild/BuildParameters.cs @@ -36,13 +36,11 @@ public partial class Build public bool IsLocalBuild { get; } public bool IsRunningOnUnix { get; } public bool IsRunningOnWindows { get; } - public bool IsRunningOnAppVeyor { get; } public bool IsRunningOnAzure { get; } public bool IsPullRequest { get; } public bool IsMainRepo { get; } public bool IsMasterBranch { get; } public bool IsReleaseBranch { get; } - public bool IsTagged { get; } public bool IsReleasable { get; } public bool IsMyGetRelease { get; } public bool IsNuGetRelease { get; } @@ -64,9 +62,6 @@ public partial class Build public BuildParameters(Build b) { - var buildSystem = Host; - - // ARGUMENTS Configuration = b.NukeArgConfiguration ?? "Release"; SkipTests = b.NukeArgSkipTests; @@ -79,77 +74,36 @@ public partial class Build MSBuildSolution = RootDirectory / "dirs.proj"; // PARAMETERS - IsLocalBuild = buildSystem == HostType.Console; + IsLocalBuild = Host == HostType.Console; IsRunningOnUnix = Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX; IsRunningOnWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - IsRunningOnAppVeyor = buildSystem == HostType.AppVeyor; - IsRunningOnAzure = buildSystem == HostType.TeamServices || + IsRunningOnAzure = Host == HostType.TeamServices || Environment.GetEnvironmentVariable("LOGNAME") == "vsts"; - string tagName = null; - if (IsRunningOnAppVeyor) - { - IsPullRequest = AppVeyor.Instance.PullRequestNumber != 0; - RepositoryName = Environment.GetEnvironmentVariable("BUILD_REPOSITORY_URI"); - RepositoryBranch = Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCH"); - - IsReleaseBranch = - (Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCH") ?? "").StartsWith(ReleaseBranchPrefix, - StringComparison.OrdinalIgnoreCase); - IsTagged = AppVeyor.Instance.RepositoryTag - && !string.IsNullOrWhiteSpace(AppVeyor.Instance.RepositoryTagName); - - tagName = AppVeyor.Instance.RepositoryTagName; - } - else if (IsRunningOnAzure) + if (IsRunningOnAzure) { RepositoryName = TeamServices.Instance.RepositoryUri; RepositoryBranch = TeamServices.Instance.SourceBranch; IsPullRequest = TeamServices.Instance.PullRequestId.HasValue; IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, TeamServices.Instance.RepositoryUri); - - // TODO??? - IsTagged = false; - tagName = null; } IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, RepositoryName); IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, RepositoryBranch); + IsReleaseBranch = RepositoryBranch?.StartsWith(ReleaseBranchPrefix, StringComparison.OrdinalIgnoreCase) == + true; - IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration); - IsMyGetRelease = !IsTagged && IsReleasable; + IsMyGetRelease = IsReleasable; IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch; // VERSION Version = b.NukeArgForceNugetVersion ?? GetVersion(); - if (IsRunningOnAppVeyor) - { - string tagVersion = null; - if (IsTagged) - { - var tag = tagName; - var nugetReleasePrefix = "nuget-release-"; - IsNuGetRelease = IsTagged && IsReleasable && tag.StartsWith(nugetReleasePrefix); - if (IsNuGetRelease) - tagVersion = tag.Substring(nugetReleasePrefix.Length); - } - - if (tagVersion != null) - { - Version = tagVersion; - } - else - { - // Use AssemblyVersion with Build as version - Version += "-build" + Environment.GetEnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta"; - } - } - else if (IsRunningOnAzure) + if (IsRunningOnAzure) { if (!IsNuGetRelease) { diff --git a/nukebuild/Shims.cs b/nukebuild/Shims.cs index 7f26490493..1ba72494d7 100644 --- a/nukebuild/Shims.cs +++ b/nukebuild/Shims.cs @@ -40,7 +40,6 @@ public partial class Build foreach (var path in paths) { - if (Directory.Exists(path)) { var dirInfo = new DirectoryInfo(path); From 076b811f2aa5ab0a3805f47aa2570d1d7850e2fa Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 2 Dec 2018 10:27:25 +0000 Subject: [PATCH 102/147] enable drop shadow on win32 popups. --- .../Interop/UnmanagedMethods.cs | 27 +++++++++++++++++-- src/Windows/Avalonia.Win32/PopupImpl.cs | 10 ++++++- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 60b56ea580..7d6e8fc8ce 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -909,8 +909,17 @@ namespace Avalonia.Win32.Interop public enum ClassLongIndex : int { - GCL_HCURSOR = -12, - GCL_HICON = -14 + GCLP_MENUNAME = -8, + GCLP_HBRBACKGROUND = -10, + GCLP_HCURSOR = -12, + GCLP_HICON = -14, + GCLP_HMODULE = -16, + GCL_CBWNDEXTRA = -18, + GCL_CBCLSEXTRA = -20, + GCLP_WNDPROC = -24, + GCL_STYLE = -26, + GCLP_HICONSM = -34, + GCW_ATOM = -32 } [DllImport("user32.dll", EntryPoint = "SetClassLongPtr")] @@ -929,6 +938,20 @@ namespace Avalonia.Win32.Interop return SetClassLong64(hWnd, nIndex, dwNewLong); } + public static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex) + { + if (IntPtr.Size > 4) + return GetClassLongPtr64(hWnd, nIndex); + else + return new IntPtr(GetClassLongPtr32(hWnd, nIndex)); + } + + [DllImport("user32.dll", EntryPoint = "GetClassLong")] + public static extern uint GetClassLongPtr32(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", EntryPoint = "GetClassLongPtr")] + public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex); + [DllImport("user32.dll", EntryPoint = "SetCursor")] internal static extern IntPtr SetCursor(IntPtr hCursor); diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index 7849bd6a9d..39f1a95466 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -24,7 +24,7 @@ namespace Avalonia.Win32 UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW | UnmanagedMethods.WindowStyles.WS_EX_TOPMOST; - return UnmanagedMethods.CreateWindowEx( + var result = UnmanagedMethods.CreateWindowEx( (int)exStyle, atom, null, @@ -37,6 +37,14 @@ namespace Avalonia.Win32 IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + var classes = (int)UnmanagedMethods.GetClassLongPtr(result, (int)UnmanagedMethods.ClassLongIndex.GCL_STYLE); + + classes |= (int)UnmanagedMethods.ClassStyles.CS_DROPSHADOW; + + UnmanagedMethods.SetClassLong(result, UnmanagedMethods.ClassLongIndex.GCL_STYLE, new IntPtr(classes)); + + return result; } protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 0b9d16daa0..56bb7347ec 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -384,7 +384,7 @@ namespace Avalonia.Win32 public void SetCursor(IPlatformHandle cursor) { var hCursor = cursor?.Handle ?? DefaultCursor; - UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCL_HCURSOR, hCursor); + UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCLP_HCURSOR, hCursor); if (_owner.IsPointerOver) UnmanagedMethods.SetCursor(hCursor); From 9bd4f35dad3ed4499f77279197e9b94f6b24bb86 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 2 Dec 2018 15:12:21 +0300 Subject: [PATCH 103/147] Removed MsBuildSettingsWithRestore hackery --- nukebuild/Build.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 63e889ae30..1014aa1b47 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -68,23 +68,14 @@ partial class Build : NukeBuild EnsureCleanDirectory(Parameters.TestResultsRoot); }); - [Serializable] - class MsBuildSettingsWithRestore : MSBuildSettings - { - protected override Arguments ConfigureArguments(Arguments arguments) - { - arguments.Add("/restore"); - return base.ConfigureArguments(arguments); - } - } - Target Compile => _ => _ .DependsOn(Clean) .Executes(() => { if (Parameters.IsRunningOnWindows) - MSBuild(Parameters.MSBuildSolution, c => new MsBuildSettingsWithRestore() + MSBuild(Parameters.MSBuildSolution, c => c + .SetArgumentConfigurator(a => a.Add("/r")) .SetConfiguration(Parameters.Configuration) .SetVerbosity(MSBuildVerbosity.Minimal) .AddProperty("PackageVersion", Parameters.Version) From f678b4c31d692f9aa9bc4a987e312ac4c07a4ce2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 4 Dec 2018 10:25:23 +0300 Subject: [PATCH 104/147] Removed appveyor badge --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 9d113cf2ef..9280125323 100644 --- a/readme.md +++ b/readme.md @@ -2,9 +2,9 @@ # Avalonia -| Gitter Chat | Build Status (Win, Linux, OSX) | Appveyor Build Status | Open Collective | -|---|---|---|---| -| [![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://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) | +| Gitter Chat | Build Status (Win, Linux, OSX) | Open Collective | +|---|---|---| +| [![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://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) | ## About From d3329e19d89f004351eb4ea060c01e9a21d827a0 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 5 Dec 2018 20:50:10 +0800 Subject: [PATCH 105/147] Simplify iteration number calculation. --- src/Avalonia.Animation/AnimationInstance`1.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 8184e68d42..52cb9e72c0 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -16,7 +16,7 @@ namespace Avalonia.Animation private T _lastInterpValue; private T _firstKFValue; private long _repeatCount; - private double _currentIteration; + private long _currentIteration; private bool _isLooping; private bool _gotFirstKFValue; private bool _iterationDelay; @@ -72,7 +72,7 @@ namespace Avalonia.Animation _onCompleteAction = OnComplete; _interpolator = Interpolator; _baseClock = baseClock; - } + } protected override void Unsubscribed() { @@ -147,45 +147,51 @@ namespace Avalonia.Animation { _currentIteration = 1; } + // time is currently the second to nth iteration. else if (time > iterationEndpoint) { - //Subtract first iteration to properly get the subsequent iteration time + // Subtract first iteration to properly get the subsequent iteration time. iterationTime -= iterationEndpoint; + // Ignore delays on subsequent iterations if it's not the initial + // iteration unless _iterationDelay is enabled. if (!_iterationDelay & delayEndpoint > TimeSpan.Zero) { delayEndpoint = TimeSpan.Zero; iterationEndpoint = _duration; } - //Calculate the current iteration number - _currentIteration = Math.Min(_repeatCount,(int)Math.Floor((double)((double)iterationTime.Ticks / iterationEndpoint.Ticks)) + 2); + // Calculate the current iteration number + _currentIteration = (iterationTime.Ticks / iterationEndpoint.Ticks) + 2; } else { return; } - // Determine if the current iteration should have its normalized time inverted. + // Determine if the current iteration should have its normalized time reversed. bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false : _animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true : _animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false : _animationDirection == PlaybackDirection.Reverse ? true : false; - + if (!_isLooping) { - var totalTime = _iterationDelay ? _repeatCount * ( _duration.Ticks + _delay.Ticks) : _repeatCount * _duration.Ticks + _delay.Ticks; + var totalTime = _iterationDelay ? _repeatCount * (_duration.Ticks + _delay.Ticks) : _repeatCount * _duration.Ticks + _delay.Ticks; + + // Clamp value when animations ends. if (time.Ticks >= totalTime) { var easedTime = _easeFunc.Ease(isCurIterReverse ? 0.0 : 1.0); _lastInterpValue = _interpolator(easedTime, _neutralValue); - + DoComplete(); return; } } - iterationTime = TimeSpan.FromTicks((long)(iterationTime.Ticks % iterationEndpoint.Ticks)); - + + iterationTime = TimeSpan.FromTicks(iterationTime.Ticks % iterationEndpoint.Ticks); + if (delayEndpoint > TimeSpan.Zero & iterationTime < delayEndpoint) { DoDelay(); @@ -199,6 +205,7 @@ namespace Avalonia.Animation // Normalize time var interpVal = (double)iterationTime.Ticks / iterationEndpoint.Ticks; + // Check if normalized time needs to be reversed. if (isCurIterReverse) interpVal = 1 - interpVal; From 52a9e72405f9c30ec597af08b876b2e524ddc49a Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Dec 2018 00:10:29 +0800 Subject: [PATCH 106/147] Simplify the animations iteration algorithm --- src/Avalonia.Animation/Animation.cs | 11 +- src/Avalonia.Animation/AnimationInstance`1.cs | 114 +++++++----------- 2 files changed, 44 insertions(+), 81 deletions(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index d7efc69e10..8b5e760455 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -54,17 +54,12 @@ namespace Avalonia.Animation /// Describes a delay to be added before the animation starts, and optionally between /// repeats of the animation if is set. /// - public TimeSpan Delay { get; set; } + public TimeSpan Delay { get; set; } = TimeSpan.Zero; /// - /// Gets or sets a value indicating whether will be applied between - /// iterations of the animation. + /// Gets or sets the amount of delay time between iterations. /// - /// - /// If this property is not set, then will only be applied to the first - /// iteration of the animation. - /// - public bool DelayBetweenIterations { get; set; } + public TimeSpan DelayBetweenIterations { get; set; } = TimeSpan.Zero; private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> { diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 52cb9e72c0..69b99f7888 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -15,18 +15,18 @@ namespace Avalonia.Animation { private T _lastInterpValue; private T _firstKFValue; - private long _repeatCount; + private float _iterationCount; private long _currentIteration; private bool _isLooping; private bool _gotFirstKFValue; - private bool _iterationDelay; private FillMode _fillMode; private PlaybackDirection _animationDirection; private Animator _parent; private Animatable _targetControl; private T _neutralValue; private double _speedRatio; - private TimeSpan _delay; + private TimeSpan _initialDelay; + private TimeSpan _iterationDelay; private TimeSpan _duration; private Easings.Easing _easeFunc; private Action _onCompleteAction; @@ -50,20 +50,20 @@ namespace Avalonia.Animation _speedRatio = animation.SpeedRatio; - _delay = animation.Delay; + _initialDelay = animation.Delay; _duration = animation.Duration; _iterationDelay = animation.DelayBetweenIterations; switch (animation.RepeatCount.RepeatType) { case RepeatType.None: - _repeatCount = 1; + _iterationCount = 1; break; case RepeatType.Loop: - _isLooping = true; + _iterationCount = float.PositiveInfinity; break; case RepeatType.Repeat: - _repeatCount = (long)animation.RepeatCount.Value; + _iterationCount = animation.RepeatCount.Value; break; } @@ -76,7 +76,7 @@ namespace Avalonia.Animation protected override void Unsubscribed() { - //Animation may have been stopped before it has finished + // Animation may have been stopped before it has finished. ApplyFinalFill(); _timerSubscription?.Dispose(); @@ -138,83 +138,51 @@ namespace Avalonia.Animation private void InternalStep(TimeSpan time) { DoPlayStates(); - var delayEndpoint = _delay; - var iterationEndpoint = delayEndpoint + _duration; - var iterationTime = time; - //determine if time is currently in the first iteration. - if (time >= TimeSpan.Zero & time <= iterationEndpoint) - { - _currentIteration = 1; - } - // time is currently the second to nth iteration. - else if (time > iterationEndpoint) - { - // Subtract first iteration to properly get the subsequent iteration time. - iterationTime -= iterationEndpoint; - - // Ignore delays on subsequent iterations if it's not the initial - // iteration unless _iterationDelay is enabled. - if (!_iterationDelay & delayEndpoint > TimeSpan.Zero) - { - delayEndpoint = TimeSpan.Zero; - iterationEndpoint = _duration; - } + var indexTime = time.Ticks; + var iterDuration = _duration.Ticks * _speedRatio; + var iterDelay = _iterationDelay.Ticks * _speedRatio; + var initDelay = _initialDelay.Ticks * _speedRatio; - // Calculate the current iteration number - _currentIteration = (iterationTime.Ticks / iterationEndpoint.Ticks) + 2; - } - else + if (indexTime > 0 & indexTime <= initDelay) { - return; + DoDelay(); } - - // Determine if the current iteration should have its normalized time reversed. - bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false : - _animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true : - _animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false : - _animationDirection == PlaybackDirection.Reverse ? true : false; - - if (!_isLooping) + else { - var totalTime = _iterationDelay ? _repeatCount * (_duration.Ticks + _delay.Ticks) : _repeatCount * _duration.Ticks + _delay.Ticks; - - // Clamp value when animations ends. - if (time.Ticks >= totalTime) - { - var easedTime = _easeFunc.Ease(isCurIterReverse ? 0.0 : 1.0); - _lastInterpValue = _interpolator(easedTime, _neutralValue); + var fullIterationTime = iterDuration + iterDelay; + var opsTime = indexTime - initDelay; + var playbackTime = opsTime % fullIterationTime; + _currentIteration = (long)Math.Floor(opsTime / fullIterationTime); + + if ((_currentIteration + 1) > _iterationCount) DoComplete(); - return; - } - } - iterationTime = TimeSpan.FromTicks(iterationTime.Ticks % iterationEndpoint.Ticks); - - if (delayEndpoint > TimeSpan.Zero & iterationTime < delayEndpoint) - { - DoDelay(); - } - else - { - // Offset the delay time - iterationTime -= delayEndpoint; - iterationEndpoint -= delayEndpoint; + if (playbackTime <= iterDuration) + { + var normalizedTime = playbackTime / iterDuration; - // Normalize time - var interpVal = (double)iterationTime.Ticks / iterationEndpoint.Ticks; + bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false : + _animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true : + _animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false : + _animationDirection == PlaybackDirection.Reverse ? true : false; - // Check if normalized time needs to be reversed. - if (isCurIterReverse) - interpVal = 1 - interpVal; + // Check if normalized time needs to be reversed. + if (isCurIterReverse) + normalizedTime = 1 - normalizedTime; - // Ease and interpolate - var easedTime = _easeFunc.Ease(interpVal); - _lastInterpValue = _interpolator(easedTime, _neutralValue); + // Ease and interpolate + var easedTime = _easeFunc.Ease(normalizedTime); + _lastInterpValue = _interpolator(easedTime, _neutralValue); - PublishNext(_lastInterpValue); + PublishNext(_lastInterpValue); + } + else if (playbackTime > iterDuration & playbackTime <= fullIterationTime & iterDelay > 0) + { + DoDelay(); + } } } } -} +} \ No newline at end of file From b489e3a935447fd54ee903bf499a1ee2d85145bc Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Dec 2018 01:19:21 +0800 Subject: [PATCH 107/147] Additional fixes for AnimationInstance. --- src/Avalonia.Animation/AnimationInstance`1.cs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index 69b99f7888..b27be609a5 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -15,9 +15,8 @@ namespace Avalonia.Animation { private T _lastInterpValue; private T _firstKFValue; - private float _iterationCount; - private long _currentIteration; - private bool _isLooping; + private ulong? _iterationCount; + private ulong _currentIteration; private bool _gotFirstKFValue; private FillMode _fillMode; private PlaybackDirection _animationDirection; @@ -58,10 +57,7 @@ namespace Avalonia.Animation { case RepeatType.None: _iterationCount = 1; - break; - case RepeatType.Loop: - _iterationCount = float.PositiveInfinity; - break; + break; case RepeatType.Repeat: _iterationCount = animation.RepeatCount.Value; break; @@ -138,7 +134,8 @@ namespace Avalonia.Animation private void InternalStep(TimeSpan time) { DoPlayStates(); - + + // Scale timebases according to speedratio. var indexTime = time.Ticks; var iterDuration = _duration.Ticks * _speedRatio; var iterDelay = _iterationDelay.Ticks * _speedRatio; @@ -150,25 +147,27 @@ namespace Avalonia.Animation } else { - var fullIterationTime = iterDuration + iterDelay; + // Calculate timebases. + var iterationTime = iterDuration + iterDelay; var opsTime = indexTime - initDelay; - var playbackTime = opsTime % fullIterationTime; + var playbackTime = opsTime % iterationTime; - _currentIteration = (long)Math.Floor(opsTime / fullIterationTime); + _currentIteration = (ulong)(opsTime / iterationTime); + // Stop animation when the current iteration is beyond the iteration count. if ((_currentIteration + 1) > _iterationCount) DoComplete(); if (playbackTime <= iterDuration) { + // Normalize time for interpolation. var normalizedTime = playbackTime / iterDuration; + // Check if normalized time needs to be reversed. bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false : _animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true : _animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false : _animationDirection == PlaybackDirection.Reverse ? true : false; - - // Check if normalized time needs to be reversed. if (isCurIterReverse) normalizedTime = 1 - normalizedTime; @@ -178,7 +177,11 @@ namespace Avalonia.Animation PublishNext(_lastInterpValue); } - else if (playbackTime > iterDuration & playbackTime <= fullIterationTime & iterDelay > 0) + else if (playbackTime > iterDuration & + playbackTime <= iterationTime & + iterDelay > 0 & + // The last iteration's trailing delay should be skipped. + (_currentIteration + 1) < _iterationCount) { DoDelay(); } From a941eaeb4e4b0c0b8d3c99d50991bad972e46ece Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Dec 2018 01:26:31 +0800 Subject: [PATCH 108/147] Make SpeedRatio bindable. --- src/Avalonia.Animation/Animation.cs | 33 +++++-- src/Avalonia.Animation/AnimationInstance`1.cs | 18 ++-- src/Avalonia.Animation/KeyFrames.cs | 33 +++++++ src/Avalonia.Visuals/Animation/CrossFade.cs | 40 +++++---- src/Avalonia.Visuals/Animation/PageSlide.cs | 86 ++++++++++--------- 5 files changed, 137 insertions(+), 73 deletions(-) create mode 100644 src/Avalonia.Animation/KeyFrames.cs diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 8b5e760455..c79ee7b8a8 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -9,14 +9,21 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Animation.Easings; using Avalonia.Collections; +using Avalonia.Metadata; namespace Avalonia.Animation { /// /// Tracks the progress of an animation. /// - public class Animation : AvaloniaList, IAnimation + public class Animation : AvaloniaObject, IAnimation { + /// + /// Gets the children of the . + /// + [Content] + public KeyFrames Children { get; } = new KeyFrames(); + /// /// Gets or sets the active time of this animation. /// @@ -42,10 +49,6 @@ namespace Avalonia.Animation /// public Easing Easing { get; set; } = new LinearEasing(); - /// - /// Gets or sets the speed multiple for this animation. - /// - public double SpeedRatio { get; set; } = 1d; /// /// Gets or sets the delay time for this animation. @@ -61,6 +64,24 @@ namespace Avalonia.Animation /// public TimeSpan DelayBetweenIterations { get; set; } = TimeSpan.Zero; + public static readonly DirectProperty SpeedRatioProperty = + AvaloniaProperty.RegisterDirect( + nameof(_speedRatio), + o => o._speedRatio, + (o, v) => o._speedRatio = v, + 1d); + + private double _speedRatio = 1d; + + /// + /// Gets or sets the speed multiple for this animation. + /// + public double SpeedRatio + { + get { return _speedRatio; } + set { SetAndRaise(SpeedRatioProperty, ref _speedRatio, value); } + } + private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> { ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ) @@ -90,7 +111,7 @@ namespace Avalonia.Animation var animatorKeyFrames = new List(); var subscriptions = new List(); - foreach (var keyframe in this) + foreach (var keyframe in Children) { foreach (var setter in keyframe) { diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index b27be609a5..b793dde8f9 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -30,7 +30,7 @@ namespace Avalonia.Animation private Easings.Easing _easeFunc; private Action _onCompleteAction; private Func _interpolator; - private IDisposable _timerSubscription; + private IDisposable _timerSub, _speedRatioSub; private readonly IClock _baseClock; private IClock _clock; @@ -47,7 +47,8 @@ namespace Avalonia.Animation _targetControl = control; _neutralValue = (T)_targetControl.GetValue(_parent.Property); - _speedRatio = animation.SpeedRatio; + _speedRatioSub = animation.GetObservable(Animation.SpeedRatioProperty) + .Subscribe(p => _speedRatio = p); _initialDelay = animation.Delay; _duration = animation.Duration; @@ -57,7 +58,7 @@ namespace Avalonia.Animation { case RepeatType.None: _iterationCount = 1; - break; + break; case RepeatType.Repeat: _iterationCount = animation.RepeatCount.Value; break; @@ -75,14 +76,15 @@ namespace Avalonia.Animation // Animation may have been stopped before it has finished. ApplyFinalFill(); - _timerSubscription?.Dispose(); + _timerSub?.Dispose(); + _speedRatioSub?.Dispose(); _clock.PlayState = PlayState.Stop; } protected override void Subscribed() { _clock = new Clock(_baseClock); - _timerSubscription = _clock.Subscribe(Step); + _timerSub = _clock.Subscribe(Step); } public void Step(TimeSpan frameTick) @@ -134,7 +136,7 @@ namespace Avalonia.Animation private void InternalStep(TimeSpan time) { DoPlayStates(); - + // Scale timebases according to speedratio. var indexTime = time.Ticks; var iterDuration = _duration.Ticks * _speedRatio; @@ -153,7 +155,7 @@ namespace Avalonia.Animation var playbackTime = opsTime % iterationTime; _currentIteration = (ulong)(opsTime / iterationTime); - + // Stop animation when the current iteration is beyond the iteration count. if ((_currentIteration + 1) > _iterationCount) DoComplete(); @@ -178,7 +180,7 @@ namespace Avalonia.Animation PublishNext(_lastInterpValue); } else if (playbackTime > iterDuration & - playbackTime <= iterationTime & + playbackTime <= iterationTime & iterDelay > 0 & // The last iteration's trailing delay should be skipped. (_currentIteration + 1) < _iterationCount) diff --git a/src/Avalonia.Animation/KeyFrames.cs b/src/Avalonia.Animation/KeyFrames.cs new file mode 100644 index 0000000000..9e3b1d9f51 --- /dev/null +++ b/src/Avalonia.Animation/KeyFrames.cs @@ -0,0 +1,33 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Avalonia.Collections; + +namespace Avalonia.Animation +{ + /// + /// A collection of s. + /// + public class KeyFrames : AvaloniaList + { + /// + /// Initializes a new instance of the class. + /// + public KeyFrames() + { + ResetBehavior = ResetBehavior.Remove; + } + + /// + /// Initializes a new instance of the class. + /// + /// The initial items in the collection. + public KeyFrames(IEnumerable items) + : base(items) + { + ResetBehavior = ResetBehavior.Remove; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Animation/CrossFade.cs b/src/Avalonia.Visuals/Animation/CrossFade.cs index d5ddf1c7f5..b07123e050 100644 --- a/src/Avalonia.Visuals/Animation/CrossFade.cs +++ b/src/Avalonia.Visuals/Animation/CrossFade.cs @@ -21,7 +21,7 @@ namespace Avalonia.Animation /// Initializes a new instance of the class. /// public CrossFade() - :this(TimeSpan.Zero) + : this(TimeSpan.Zero) { } @@ -33,30 +33,36 @@ namespace Avalonia.Animation { _fadeOutAnimation = new Animation { - new KeyFrame - ( - new Setter + Children = + { + new KeyFrame + ( + new Setter + { + Property = Visual.OpacityProperty, + Value = 0d + } + ) { - Property = Visual.OpacityProperty, - Value = 0d + Cue = new Cue(1d) } - ) - { - Cue = new Cue(1d) } }; _fadeInAnimation = new Animation { - new KeyFrame - ( - new Setter + Children = + { + new KeyFrame + ( + new Setter + { + Property = Visual.OpacityProperty, + Value = 0d + } + ) { - Property = Visual.OpacityProperty, - Value = 0d + Cue = new Cue(0d) } - ) - { - Cue = new Cue(0d) } }; _fadeOutAnimation.Duration = _fadeInAnimation.Duration = duration; diff --git a/src/Avalonia.Visuals/Animation/PageSlide.cs b/src/Avalonia.Visuals/Animation/PageSlide.cs index c5d43068c1..c1848d7daa 100644 --- a/src/Avalonia.Visuals/Animation/PageSlide.cs +++ b/src/Avalonia.Visuals/Animation/PageSlide.cs @@ -74,34 +74,34 @@ namespace Avalonia.Animation var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height; var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty; - - // TODO: Implement relevant transition logic here (or discard this class) - // in favor of XAML based transition for pages if (from != null) { var animation = new Animation { - new KeyFrame - ( - new Setter - { - Property = translateProperty, - Value = 0d - } - ) + Children = { - Cue = new Cue(0d) - }, - new KeyFrame - ( - new Setter + new KeyFrame + ( + new Setter + { + Property = translateProperty, + Value = 0d + } + ) { - Property = translateProperty, - Value = forward ? -distance : distance + Cue = new Cue(0d) + }, + new KeyFrame + ( + new Setter + { + Property = translateProperty, + Value = forward ? -distance : distance + } + ) + { + Cue = new Cue(1d) } - ) - { - Cue = new Cue(1d) } }; animation.Duration = Duration; @@ -113,29 +113,31 @@ namespace Avalonia.Animation to.IsVisible = true; var animation = new Animation { - - new KeyFrame - ( - new Setter - { - Property = translateProperty, - Value = forward ? distance : -distance - } - ) + Children = { - Cue = new Cue(0d) - }, - new KeyFrame - ( - new Setter + new KeyFrame + ( + new Setter + { + Property = translateProperty, + Value = forward ? distance : -distance + } + ) { - Property = translateProperty, - Value = 0d - } - ) - { - Cue = new Cue(1d) - }, + Cue = new Cue(0d) + }, + new KeyFrame + ( + new Setter + { + Property = translateProperty, + Value = 0d + } + ) + { + Cue = new Cue(1d) + }, + } }; animation.Duration = Duration; tasks.Add(animation.RunAsync(to)); From 7e023ee5af1e531373f3615cae7a15582c120822 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Dec 2018 01:45:55 +0800 Subject: [PATCH 109/147] [Breaking Change] Replace RepeatCount with IterationCount. --- samples/RenderDemo/Pages/AnimationsPage.xaml | 4 +- samples/RenderDemo/Pages/ClippingPage.xaml | 2 +- src/Avalonia.Animation/Animation.cs | 4 +- src/Avalonia.Animation/AnimationInstance`1.cs | 13 +- src/Avalonia.Animation/IterationCount.cs | 176 ++++++++++++++++ ...rter.cs => IterationCountTypeConverter.cs} | 4 +- src/Avalonia.Animation/RepeatCount.cs | 199 ------------------ src/Avalonia.Themes.Default/ProgressBar.xaml | 4 +- 8 files changed, 188 insertions(+), 218 deletions(-) create mode 100644 src/Avalonia.Animation/IterationCount.cs rename src/Avalonia.Animation/{RepeatCountTypeConverter.cs => IterationCountTypeConverter.cs} (83%) delete mode 100644 src/Avalonia.Animation/RepeatCount.cs diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml index 1646708797..9f8e7b5fa5 100644 --- a/samples/RenderDemo/Pages/AnimationsPage.xaml +++ b/samples/RenderDemo/Pages/AnimationsPage.xaml @@ -43,7 +43,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + @@ -121,6 +149,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + From 87e8bca8793c40b5b320f611213122ee9d343a4a Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Dec 2018 01:03:12 +0800 Subject: [PATCH 136/147] Minor fixes. --- samples/RenderDemo/Pages/AnimationsPage.xaml | 2 +- .../Animation/Animators/SolidColorBrushAnimator.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml index e6e0eff941..53f8e855e0 100644 --- a/samples/RenderDemo/Pages/AnimationsPage.xaml +++ b/samples/RenderDemo/Pages/AnimationsPage.xaml @@ -149,7 +149,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - + diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 43cc9d5476..518d3d2ca7 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -7,8 +7,7 @@ using Avalonia.Media.Immutable; namespace Avalonia.Animation.Animators { /// - /// Animator that interpolates through - /// gamma sRGB color space for better visual result. + /// Animator that handles . /// public class SolidColorBrushAnimator : Animator { From 13eb3655714558ed3d66f34b83e1d6ee136b6ccf Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Dec 2018 11:54:38 +0800 Subject: [PATCH 137/147] Fix SCBA. --- .../Animators/SolidColorBrushAnimator.cs | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 518d3d2ca7..c3de578959 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -13,53 +13,62 @@ namespace Avalonia.Animation.Animators { ColorAnimator colorAnimator; + void InitializeColorAnimator() + { + colorAnimator = new ColorAnimator(); + + foreach (AnimatorKeyFrame keyframe in this) + { + colorAnimator.Add(keyframe); + } + + colorAnimator.Property = SolidColorBrush.ColorProperty; + } + public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete) { var ctrl = (Visual)control; foreach (var keyframe in this) { - // Return if the keyframe value is not a SolidColorBrush if (keyframe.Value as ISolidColorBrush == null) - { return Disposable.Empty; - } - // Preprocess values to Color if the xaml parser converts them to ISCB + // Preprocess keyframe values to Color if the xaml parser converts them to ISCB. if (keyframe.Value.GetType() == typeof(ImmutableSolidColorBrush)) { keyframe.Value = ((ImmutableSolidColorBrush)keyframe.Value).Color; } } - // Make sure that the target property has SCB instead of the immutable one nor null. + // Add SCB if the target prop is empty. + if (control.GetValue(Property) == null) + control.SetValue(Property, new SolidColorBrush(Colors.Transparent)); var targetVal = control.GetValue(Property); - SolidColorBrush targetSCB = null; - - if (targetVal == null) - targetSCB = new SolidColorBrush(Colors.Transparent); - else if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType())) - targetSCB = new SolidColorBrush(((ISolidColorBrush)targetVal).Color); - else - return Disposable.Empty; - - control.SetValue(Property, targetSCB); - - if (colorAnimator == null) + // Continue if target prop is not empty & is a SolidColorBrush derivative. + if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType())) { - colorAnimator = new ColorAnimator(); + if (colorAnimator == null) + InitializeColorAnimator(); + + SolidColorBrush finalTarget; - foreach (AnimatorKeyFrame keyframe in this) + // If it's ISCB, change it back to SCB. + if (targetVal.GetType() == typeof(ImmutableSolidColorBrush)) { - colorAnimator.Add(keyframe); + var col = (ImmutableSolidColorBrush)targetVal; + targetVal = new SolidColorBrush(col.Color); + control.SetValue(Property, targetVal); } - colorAnimator.Property = SolidColorBrush.ColorProperty; + finalTarget = targetVal as SolidColorBrush; + + return colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete); } - return colorAnimator.Apply(animation, targetSCB, clock ?? control.Clock, match, onComplete); + return Disposable.Empty; } public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null; From cfe26a787df7f2869643e130f16a7275f43f7f01 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 7 Dec 2018 22:25:52 +0300 Subject: [PATCH 138/147] Enable GPU acceleration on Linux by default --- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 8444e509fd..965973d3cb 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -50,7 +50,7 @@ namespace Avalonia.Gtk3 { Resolver.Custom = options.CustomResolver; UseDeferredRendering = EnvOption("USE_DEFERRED_RENDERING", true, options.UseDeferredRendering); - var useGpu = EnvOption("USE_GPU", false, options.UseGpuAcceleration); + var useGpu = EnvOption("USE_GPU", true, options.UseGpuAcceleration); if (!s_gtkInitialized) { try From ea7dc760cc22af125a8d9fdfca46c36dfb0d7a5f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 10 Dec 2018 10:18:52 +0300 Subject: [PATCH 139/147] Updated SkiaSharp to 1.68.0 --- build/Magick.NET-Q16-AnyCPU.props | 2 +- build/SkiaSharp.props | 4 ++-- nukebuild/Build.cs | 5 ++-- .../Avalonia.Skia/FramebufferRenderTarget.cs | 3 ++- src/Skia/Avalonia.Skia/GlRenderTarget.cs | 23 ++++++++----------- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 2 +- .../Controls/BorderTests.cs | 12 +++++----- tests/Avalonia.RenderTests/TestBase.cs | 1 + tests/Avalonia.RenderTests/TestSkip.cs | 21 +++++++++++++++++ 9 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 tests/Avalonia.RenderTests/TestSkip.cs diff --git a/build/Magick.NET-Q16-AnyCPU.props b/build/Magick.NET-Q16-AnyCPU.props index 4e600a1c11..21d9cdcb1f 100644 --- a/build/Magick.NET-Q16-AnyCPU.props +++ b/build/Magick.NET-Q16-AnyCPU.props @@ -1,5 +1,5 @@ - + diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index 35c979a95e..a43c99e978 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,6 +1,6 @@  - - + + diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 61944c4dbc..1e1becb1c4 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -138,12 +138,13 @@ partial class Build : NukeBuild }); Target RunRenderTests => _ => _ - .OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows) + .OnlyWhen(() => !Parameters.SkipTests) .DependsOn(Compile) .Executes(() => { RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", true); - RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", true); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", true); }); Target RunDesignerTests => _ => _ diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 5efbc0861e..02b6188fb6 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -42,7 +42,8 @@ namespace Avalonia.Skia { var framebuffer = _platformSurface.Lock(); var framebufferImageInfo = new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height, - framebuffer.Format.ToSkColorType(), SKAlphaType.Premul); + framebuffer.Format.ToSkColorType(), + framebuffer.Format == PixelFormat.Rgb565 ? SKAlphaType.Opaque : SKAlphaType.Premul); CreateSurface(framebufferImageInfo, framebuffer); diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs index a6269473a6..3360255dae 100644 --- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs @@ -31,24 +31,18 @@ namespace Avalonia.Skia var size = session.Size; var scaling = session.Scaling; - GRBackendRenderTargetDesc desc = new GRBackendRenderTargetDesc - { - Width = size.Width, - Height = size.Height, - SampleCount = disp.SampleCount, - StencilBits = disp.StencilSize, - Config = GRPixelConfig.Rgba8888, - Origin=GRSurfaceOrigin.BottomLeft, - RenderTargetHandle = new IntPtr(fb) - }; - - gl.Viewport(0, 0, desc.Width, desc.Height); + gl.Viewport(0, 0, size.Width, size.Height); gl.ClearStencil(0); gl.ClearColor(0, 0, 0, 0); gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - var surface = SKSurface.Create(_grContext, desc); - + GRBackendRenderTarget renderTarget = + new GRBackendRenderTarget(size.Width, size.Height, disp.SampleCount, disp.StencilSize, + new GRGlFramebufferInfo((uint)fb, GRPixelConfig.Rgba8888.ToGlSizedFormat())); + var surface = SKSurface.Create(_grContext, renderTarget, + GRSurfaceOrigin.BottomLeft, + GRPixelConfig.Rgba8888.ToColorType()); + var nfo = new DrawingContextImpl.CreateInfo { GrContext = _grContext, @@ -62,6 +56,7 @@ namespace Avalonia.Skia { surface.Canvas.Flush(); surface.Dispose(); + renderTarget.Dispose(); session.Dispose(); })); } diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 36ccd7930a..eb3a11b2a1 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -41,7 +41,7 @@ namespace Avalonia.Skia var nfo = new SKImageInfo(size.Width, size.Height, colorType, SKAlphaType.Premul); var blob = runtimePlatform.AllocBlob(nfo.BytesSize); - _bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, s_releaseDelegate, blob); + _bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, s_releaseDelegate, blob); } else { diff --git a/tests/Avalonia.RenderTests/Controls/BorderTests.cs b/tests/Avalonia.RenderTests/Controls/BorderTests.cs index c82d616094..4f4004e159 100644 --- a/tests/Avalonia.RenderTests/Controls/BorderTests.cs +++ b/tests/Avalonia.RenderTests/Controls/BorderTests.cs @@ -188,7 +188,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls } - [Fact] + [Win32Fact("Has text")] public async Task Border_Centers_Content_Horizontally() { Decorator target = new Decorator @@ -215,7 +215,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Centers_Content_Vertically() { Decorator target = new Decorator @@ -296,7 +296,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Left_Aligns_Content() { Decorator target = new Decorator @@ -323,7 +323,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Right_Aligns_Content() { Decorator target = new Decorator @@ -350,7 +350,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Top_Aligns_Content() { Decorator target = new Decorator @@ -377,7 +377,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls CompareImages(); } - [Fact] + [Win32Fact("Has text")] public async Task Border_Bottom_Aligns_Content() { Decorator target = new Decorator diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 4c8abd85f7..2efd28c2d5 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -46,6 +46,7 @@ namespace Avalonia.Direct2D1.RenderTests public TestBase(string outputPath) { + outputPath = outputPath.Replace('\\', Path.DirectorySeparatorChar); var testPath = GetTestsDirectory(); var testFiles = Path.Combine(testPath, "TestFiles"); #if AVALONIA_SKIA diff --git a/tests/Avalonia.RenderTests/TestSkip.cs b/tests/Avalonia.RenderTests/TestSkip.cs new file mode 100644 index 0000000000..75407332e3 --- /dev/null +++ b/tests/Avalonia.RenderTests/TestSkip.cs @@ -0,0 +1,21 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests +#endif +{ + public class Win32Fact : FactAttribute + { + public Win32Fact(string message) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Skip = message; + } + } +} + From 739309c28f116f3f9ce56a70a884dd330c879393 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Dec 2018 15:58:53 +0800 Subject: [PATCH 140/147] Replace binary-search on keyframe selection with naive approach for now. --- .../Animators/Animator`1.cs | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Animation/Animators/Animator`1.cs b/src/Avalonia.Animation/Animators/Animator`1.cs index e79aa128d6..e42489d6a6 100644 --- a/src/Avalonia.Animation/Animators/Animator`1.cs +++ b/src/Avalonia.Animation/Animators/Animator`1.cs @@ -78,7 +78,7 @@ namespace Avalonia.Animation.Animators double t0 = firstKeyframe.Cue.CueValue; double t1 = lastKeyframe.Cue.CueValue; - double progress = (animationTime - t0) / (t1 - t0); + double progress = (animationTime - t0) / (t1 - t0); T oldValue, newValue; @@ -97,30 +97,11 @@ namespace Avalonia.Animation.Animators private int FindClosestBeforeKeyFrame(double time) { - int FindClosestBeforeKeyFrame(int startIndex, int length) - { - if (length == 0 || length == 1) - { - return startIndex; - } - - int middle = startIndex + (length / 2); + for (int i = 0; i < _convertedKeyframes.Count; i++) + if (_convertedKeyframes[i].Cue.CueValue > time) + return i - 1; - if (_convertedKeyframes[middle].Cue.CueValue < time) - { - return FindClosestBeforeKeyFrame(middle, length - middle); - } - else if (_convertedKeyframes[middle].Cue.CueValue > time) - { - return FindClosestBeforeKeyFrame(startIndex, middle - startIndex); - } - else - { - return middle; - } - } - - return FindClosestBeforeKeyFrame(0, _convertedKeyframes.Count); + throw new Exception("Index time is out of keyframe time range."); } /// @@ -139,13 +120,10 @@ namespace Avalonia.Animation.Animators } /// - /// Interpolates a value given the desired time. + /// Interpolates in-between two key values given the desired progress time. /// public abstract T Interpolate(double progress, T oldValue, T newValue); - /// - /// Verifies, converts and sorts keyframe values according to this class's target type. - /// private void VerifyConvertKeyFrames() { foreach (AnimatorKeyFrame keyframe in this) @@ -193,4 +171,4 @@ namespace Avalonia.Animation.Animators } } } -} \ No newline at end of file +} From 2fe9378ebc79b0f7f32a45bca84c15b514d54b3f Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 10 Dec 2018 16:01:21 +0800 Subject: [PATCH 141/147] Adjust rainbow example --- samples/RenderDemo/Pages/AnimationsPage.xaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml index 53f8e855e0..b5a8ff6dea 100644 --- a/samples/RenderDemo/Pages/AnimationsPage.xaml +++ b/samples/RenderDemo/Pages/AnimationsPage.xaml @@ -106,19 +106,20 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">