diff --git a/.ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject b/.ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 52a400d19a..ea09278cdd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: csharp os: - linux - - osx dist: trusty osx_image: xcode8.3 env: diff --git a/scripts/ReplaceNugetCache.ps1 b/scripts/ReplaceNugetCache.ps1 index 5f678bedef..4ad31db274 100644 --- a/scripts/ReplaceNugetCache.ps1 +++ b/scripts/ReplaceNugetCache.ps1 @@ -1,5 +1,4 @@ -copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp1.0\ -copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard1.3\ -copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard1.3\ -copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.skia.desktop\$args\lib\netstandard1.3\ -copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard1.3\ +copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\ +copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\ +copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\ +copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\ diff --git a/scripts/ReplaceNugetCache.sh b/scripts/ReplaceNugetCache.sh index 2ce3e7648d..636aec5f23 100755 --- a/scripts/ReplaceNugetCache.sh +++ b/scripts/ReplaceNugetCache.sh @@ -1,7 +1,6 @@ #!/usr/bin/env bash - cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp1.0/ - cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard1.1/ - cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard1.1/ - cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.skia.desktop/$1/lib/netstandard1.3/ + cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp2.0/ + cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard2.0/ + cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard2.0/ diff --git a/scripts/ReplaceNugetCacheRelease.ps1 b/scripts/ReplaceNugetCacheRelease.ps1 index f3c1a0a975..f188c81c51 100644 --- a/scripts/ReplaceNugetCacheRelease.ps1 +++ b/scripts/ReplaceNugetCacheRelease.ps1 @@ -1,5 +1,4 @@ -copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp1.0\ -copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard1.3\ -copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard1.3\ -copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.skia.desktop\$args\lib\netstandard1.3\ -copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard1.3\ +copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\ +copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\ +copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\ +copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\ \ No newline at end of file diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index f3705c9127..14beb2eab7 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -656,7 +656,6 @@ namespace Avalonia.Controls /// The event args. protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { - AttachedToLogicalTree?.Invoke(this, e); } /// @@ -665,7 +664,6 @@ namespace Avalonia.Controls /// The event args. protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { - DetachedFromLogicalTree?.Invoke(this, e); } /// @@ -773,11 +771,9 @@ namespace Avalonia.Controls { while (e != null) { - var root = e as IStyleRoot; - - if (root != null && root.StylingParent == null) + if (e is IRenderRoot root) { - return root; + return root as IStyleRoot; } e = e.StylingParent; @@ -844,6 +840,7 @@ namespace Avalonia.Controls InitializeStylesIfNeeded(true); OnAttachedToLogicalTree(e); + AttachedToLogicalTree?.Invoke(this, e); } foreach (var child in LogicalChildren.OfType()) @@ -864,6 +861,7 @@ namespace Avalonia.Controls _isAttachedToLogicalTree = false; _styleDetach.OnNext(this); OnDetachedFromLogicalTree(e); + DetachedFromLogicalTree?.Invoke(this, e); foreach (var child in LogicalChildren.OfType()) { diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index 5349fb1ca7..fa2e0c1e16 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -120,21 +120,21 @@ namespace Avalonia.Controls /// protected override void OnPointerPressed(PointerPressedEventArgs e) { - if (!IsDropDownOpen && ((IVisual)e.Source).GetVisualRoot() is PopupRoot) - { - IsDropDownOpen = true; - e.Handled = true; - } - if (!e.Handled) { - if (UpdateSelectionFromEventSource(e.Source)) + if (((IVisual)e.Source).GetVisualRoot() is PopupRoot) { - _popup?.Close(); - e.Handled = true; + if (UpdateSelectionFromEventSource(e.Source)) + { + _popup?.Close(); + e.Handled = true; + } + } + else + { + IsDropDownOpen = !IsDropDownOpen; } } - base.OnPointerPressed(e); } diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 875a5ebaee..50cdb5945c 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -20,6 +20,8 @@ namespace Avalonia.Input private int _clickCount; private Rect _lastClickRect; private uint _lastClickTime; + private IInputElement _captured; + private IDisposable _capturedSubscription; /// /// Gets the control that is currently capturing by the mouse, if any. @@ -31,8 +33,23 @@ namespace Avalonia.Input /// public IInputElement Captured { - get; - protected set; + get => _captured; + protected set + { + _capturedSubscription?.Dispose(); + _capturedSubscription = null; + + if (value != null) + { + _capturedSubscription = Observable.FromEventPattern( + x => value.DetachedFromVisualTree += x, + x => value.DetachedFromVisualTree -= x) + .Take(1) + .Subscribe(_ => Captured = null); + } + + _captured = value; + } } /// @@ -55,6 +72,7 @@ namespace Avalonia.Input /// public virtual void Capture(IInputElement control) { + // TODO: Check visibility and enabled state before setting capture. Captured = control; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index a85fd36763..a4af106a73 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -173,7 +173,7 @@ namespace Avalonia.Rendering.SceneGraph } } - if (node.HitTest(p)) + if (node.HitTest(p) && node.Visual.IsAttachedToVisualTree) { yield return node.Visual; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index 10455eb147..90ef78de37 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -168,11 +168,14 @@ namespace Avalonia.Rendering.SceneGraph using (context.PushTransformContainer()) { var startLayer = opacity < 1 || visual.OpacityMask != null; + var clipBounds = bounds.TransformToAABB(contextImpl.Transform).Intersect(clip); - forceRecurse = forceRecurse || node.Transform != contextImpl.Transform; + forceRecurse = forceRecurse || + node.Transform != contextImpl.Transform || + node.ClipBounds != clipBounds; node.Transform = contextImpl.Transform; - node.ClipBounds = bounds.TransformToAABB(node.Transform).Intersect(clip); + node.ClipBounds = clipBounds; node.ClipToBounds = clipToBounds; node.GeometryClip = visual.Clip?.PlatformImpl; node.Opacity = opacity; diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index cfe6bce7e0..bc65d4f69f 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -329,6 +329,7 @@ namespace Avalonia } OnAttachedToVisualTree(e); + AttachedToVisualTree?.Invoke(this, e); InvalidateVisual(); if (VisualChildren != null) @@ -357,6 +358,7 @@ namespace Avalonia } OnDetachedFromVisualTree(e); + DetachedFromVisualTree?.Invoke(this, e); e.Root?.Renderer?.AddDirty(this); if (VisualChildren != null) @@ -374,7 +376,6 @@ namespace Avalonia /// The event args. protected virtual void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - AttachedToVisualTree?.Invoke(this, e); } /// @@ -383,7 +384,6 @@ namespace Avalonia /// The event args. protected virtual void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { - DetachedFromVisualTree?.Invoke(this, e); } /// diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests.cs b/tests/Avalonia.Controls.UnitTests/ControlTests.cs index 605406518d..b7e2cbeca9 100644 --- a/tests/Avalonia.Controls.UnitTests/ControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ControlTests.cs @@ -123,7 +123,26 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void DetachedToLogicalParent_Should_Be_Called_When_Removed_From_Tree() + public void AttachedToLogicalParent_Should_Not_Be_Called_With_GlobalStyles_As_Root() + { + var globalStyles = Mock.Of(); + var root = new TestRoot { StylingParent = globalStyles }; + var child = new Border(); + var raised = false; + + child.AttachedToLogicalTree += (s, e) => + { + Assert.Equal(root, e.Root); + raised = true; + }; + + root.Child = child; + + Assert.True(raised); + } + + [Fact] + public void DetachedFromLogicalParent_Should_Be_Called_When_Removed_From_Tree() { var root = new TestRoot(); var parent = new Border(); @@ -148,6 +167,26 @@ namespace Avalonia.Controls.UnitTests Assert.True(grandchildRaised); } + [Fact] + public void DetachedFromLogicalParent_Should_Not_Be_Called_With_GlobalStyles_As_Root() + { + var globalStyles = Mock.Of(); + var root = new TestRoot { StylingParent = globalStyles }; + var child = new Border(); + var raised = false; + + child.DetachedFromLogicalTree += (s, e) => + { + Assert.Equal(root, e.Root); + raised = true; + }; + + root.Child = child; + root.Child = null; + + Assert.True(raised); + } + [Fact] public void Adding_Tree_To_IStyleRoot_Should_Style_Controls() { diff --git a/tests/Avalonia.Controls.UnitTests/DropDownTests.cs b/tests/Avalonia.Controls.UnitTests/DropDownTests.cs index 30cca90b4a..b5de8c67fa 100644 --- a/tests/Avalonia.Controls.UnitTests/DropDownTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DropDownTests.cs @@ -1,21 +1,43 @@ // 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.Linq; 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; using Avalonia.UnitTests; -using Avalonia.VisualTree; using Xunit; namespace Avalonia.Controls.UnitTests { public class DropDownTests { + [Fact] + public void Clicking_On_Control_Toggles_IsDropDownOpen() + { + var target = new DropDown + { + Items = new[] { "Foo", "Bar" }, + }; + + target.RaiseEvent(new PointerPressedEventArgs + { + RoutedEvent = InputElement.PointerPressedEvent, + }); + + Assert.True(target.IsDropDownOpen); + + target.RaiseEvent(new PointerPressedEventArgs + { + RoutedEvent = InputElement.PointerPressedEvent, + }); + + Assert.False(target.IsDropDownOpen); + } + [Fact] public void SelectionBoxItem_Is_Rectangle_With_VisualBrush_When_Selection_Is_Control() { diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs index 032928d673..027e162877 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs @@ -12,6 +12,7 @@ using Moq; using System; using System.Linq; using Xunit; +using Avalonia.Rendering; namespace Avalonia.Controls.UnitTests.Presenters { @@ -56,6 +57,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var parentMock = new Mock(); parentMock.As(); + parentMock.As(); parentMock.As(); (target as ISetLogicalParent).SetParent(parentMock.Object); @@ -100,6 +102,7 @@ namespace Avalonia.Controls.UnitTests.Presenters }; var parentMock = new Mock(); + parentMock.As(); parentMock.As(); parentMock.As().SetupGet(l => l.IsAttachedToLogicalTree).Returns(true); @@ -144,6 +147,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var parentMock = new Mock(); parentMock.As(); + parentMock.As(); parentMock.As(); (target as ISetLogicalParent).SetParent(parentMock.Object); diff --git a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs index 4a834fd55d..6aca69b88f 100644 --- a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs @@ -1,6 +1,5 @@ using Avalonia.Controls; using Avalonia.Input.Raw; -using Avalonia.Layout; using Avalonia.Rendering; using Avalonia.UnitTests; using Avalonia.VisualTree; @@ -12,6 +11,24 @@ namespace Avalonia.Input.UnitTests { public class MouseDeviceTests { + [Fact] + public void Capture_Is_Cleared_When_Control_Removed() + { + Canvas control; + var root = new TestRoot + { + Child = control = new Canvas(), + }; + var target = new MouseDevice(); + + target.Capture(control); + Assert.Same(control, target.Captured); + + root.Child = null; + + Assert.Null(target.Captured); + } + [Fact] public void MouseMove_Should_Update_PointerOver() { diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index c51e8941a9..d0f7671956 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -143,13 +143,61 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var borderNode = scene.FindNode(border); Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds); + } + } + + [Fact] + public void Should_Update_Descendent_ClipBounds_When_Margin_Changed() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + Border border; + Canvas canvas; + var tree = new TestRoot + { + Width = 200, + Height = 300, + Child = canvas = new Canvas + { + ClipToBounds = true, + Width = 100, + Height = 100, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + Children = + { + (border = new Border + { + Background = Brushes.AliceBlue, + Width = 100, + Height = 100, + [Canvas.LeftProperty] = 50, + [Canvas.TopProperty] = 50, + }) + } + } + }; + + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(tree.DesiredSize)); - // Initial ClipBounds are correct, make sure they're still correct after updating border. + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var borderNode = scene.FindNode(border); + Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds); + + canvas.Width = canvas.Height = 125; + canvas.Measure(Size.Infinity); + canvas.Arrange(new Rect(tree.DesiredSize)); + + // Initial ClipBounds are correct, make sure they're still correct after updating canvas. scene = scene.Clone(); - Assert.True(sceneBuilder.Update(scene, border)); + Assert.True(sceneBuilder.Update(scene, canvas)); borderNode = scene.FindNode(border); - Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds); + Assert.Equal(new Rect(50, 50, 75, 75), borderNode.ClipBounds); } }