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);
}
}