Browse Source

Allow reuse of existing tooltip popup.

To do this, had to fix a problem where templated children weren't notified of being re-attached to a logical tree.
pull/1031/head
Steven Kirk 9 years ago
parent
commit
a232b137b5
  1. 11
      src/Avalonia.Controls/Control.cs
  2. 11
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  3. 21
      src/Avalonia.Controls/ToolTip.cs
  4. 10
      src/Avalonia.Styling/LogicalTree/ILogical.cs
  5. 109
      tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
  6. 28
      tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs
  7. 19
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  8. 5
      tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs

11
src/Avalonia.Controls/Control.cs

@ -118,6 +118,7 @@ namespace Avalonia.Controls
public Control() public Control()
{ {
_nameScope = this as INameScope; _nameScope = this as INameScope;
_isAttachedToLogicalTree = this is IStyleRoot;
} }
/// <summary> /// <summary>
@ -369,6 +370,12 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc/>
void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
this.OnAttachedToLogicalTreeCore(e);
}
/// <inheritdoc/> /// <inheritdoc/>
void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
@ -418,7 +425,7 @@ namespace Avalonia.Controls
if (_isAttachedToLogicalTree) if (_isAttachedToLogicalTree)
{ {
var oldRoot = FindStyleRoot(old); var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
if (oldRoot == null) if (oldRoot == null)
{ {
@ -436,7 +443,7 @@ namespace Avalonia.Controls
_parent = (IControl)parent; _parent = (IControl)parent;
if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true) if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
{ {
var newRoot = FindStyleRoot(this); var newRoot = FindStyleRoot(this);

11
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -285,6 +285,17 @@ namespace Avalonia.Controls.Primitives
return this; return this;
} }
/// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (VisualChildren.Count > 0)
{
((ILogical)VisualChildren[0]).NotifyAttachedToLogicalTree(e);
}
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {

21
src/Avalonia.Controls/ToolTip.cs

@ -108,10 +108,18 @@ namespace Avalonia.Controls
var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control); var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control);
var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22); var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22);
DisposeTooltip(); if (s_popup == null)
s_popup = new PopupRoot(); {
s_popup = new PopupRoot();
s_popup.Content = new ToolTip();
}
else
{
((ISetLogicalParent)s_popup).SetParent(null);
}
((ISetLogicalParent)s_popup).SetParent(control); ((ISetLogicalParent)s_popup).SetParent(control);
s_popup.Content = new ToolTip { Content = GetTip(control) }; ((ToolTip)s_popup.Content).Content = GetTip(control);
s_popup.Position = position; s_popup.Position = position;
s_popup.Show(); s_popup.Show();
@ -141,8 +149,11 @@ namespace Avalonia.Controls
if (control == s_current) if (control == s_current)
{ {
DisposeTooltip(); if (s_popup != null)
s_show.OnNext(null); {
DisposeTooltip();
s_show.OnNext(null);
}
} }
} }

10
src/Avalonia.Styling/LogicalTree/ILogical.cs

@ -36,6 +36,16 @@ namespace Avalonia.LogicalTree
/// </summary> /// </summary>
IAvaloniaReadOnlyList<ILogical> LogicalChildren { get; } IAvaloniaReadOnlyList<ILogical> LogicalChildren { get; }
/// <summary>
/// Notifies the control that it is being attached to a rooted logical tree.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// This method will be called automatically by the framework, you should not need to call
/// this method yourself.
/// </remarks>
void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e);
/// <summary> /// <summary>
/// Notifies the control that it is being detached from a rooted logical tree. /// Notifies the control that it is being detached from a rooted logical tree.
/// </summary> /// </summary>

109
tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

@ -0,0 +1,109 @@
// 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 Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.LogicalTree;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests.Primitives
{
public class PopupRootTests
{
[Fact]
public void PopupRoot_IsAttachedToLogicalTree_Is_True()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = CreateTarget();
Assert.True(((ILogical)target).IsAttachedToLogicalTree);
}
}
[Fact]
public void Templated_Child_IsAttachedToLogicalTree_Is_True()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = CreateTarget();
Assert.True(target.Presenter.IsAttachedToLogicalTree);
}
}
[Fact]
public void Attaching_PopupRoot_To_Parent_Logical_Tree_Raises_DetachedFromLogicalTree_And_AttachedToLogicalTree()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new Decorator();
var target = CreateTarget();
var window = new Window();
var detachedCount = 0;
var attachedCount = 0;
target.Content = child;
target.DetachedFromLogicalTree += (s, e) => ++detachedCount;
child.DetachedFromLogicalTree += (s, e) => ++detachedCount;
target.AttachedToLogicalTree += (s, e) => ++attachedCount;
child.AttachedToLogicalTree += (s, e) => ++attachedCount;
((ISetLogicalParent)target).SetParent(window);
Assert.Equal(2, detachedCount);
Assert.Equal(2, attachedCount);
}
}
[Fact]
public void Detaching_PopupRoot_From_Parent_Logical_Tree_Raises_DetachedFromLogicalTree_And_AttachedToLogicalTree()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new Decorator();
var target = CreateTarget();
var window = new Window();
var detachedCount = 0;
var attachedCount = 0;
target.Content = child;
((ISetLogicalParent)target).SetParent(window);
target.DetachedFromLogicalTree += (s, e) => ++detachedCount;
child.DetachedFromLogicalTree += (s, e) => ++detachedCount;
target.AttachedToLogicalTree += (s, e) => ++attachedCount;
child.AttachedToLogicalTree += (s, e) => ++attachedCount;
((ISetLogicalParent)target).SetParent(null);
// Despite being detached from the parent logical tree, we're still attached to a
// logical tree as PopupRoot itself is a logical tree root.
Assert.True(((ILogical)target).IsAttachedToLogicalTree);
Assert.True(((ILogical)child).IsAttachedToLogicalTree);
Assert.Equal(2, detachedCount);
Assert.Equal(2, attachedCount);
}
}
private PopupRoot CreateTarget()
{
var result = new PopupRoot
{
Template = new FuncControlTemplate<PopupRoot>(_ =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
}),
};
result.ApplyTemplate();
return result;
}
}
}

28
tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs

@ -527,6 +527,34 @@ namespace Avalonia.Controls.UnitTests.Primitives
} }
} }
[Fact]
public void Moving_To_New_LogicalTree_Should_Detach_Attach_Template_Child()
{
using (UnitTestApplication.Start(TestServices.RealStyler))
{
TestTemplatedControl target;
var root = new TestRoot
{
Child = target = new TestTemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator()),
}
};
Assert.NotNull(target.Template);
target.ApplyTemplate();
var templateChild = (ILogical)target.GetVisualChildren().Single();
Assert.True(templateChild.IsAttachedToLogicalTree);
root.Child = null;
Assert.False(templateChild.IsAttachedToLogicalTree);
var newRoot = new TestRoot { Child = target };
Assert.True(templateChild.IsAttachedToLogicalTree);
}
}
private static IControl ScrollingContentControlTemplate(ContentControl control) private static IControl ScrollingContentControlTemplate(ContentControl control)
{ {
return new Border return new Border

19
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -2,24 +2,33 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Reactive;
using System.Reactive.Subjects;
using Moq;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq;
using Xunit; using Xunit;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
{ {
public class TopLevelTests public class TopLevelTests
{ {
[Fact]
public void IsAttachedToLogicalTree_Is_True()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var target = new TestTopLevel(impl.Object);
Assert.True(((ILogical)target).IsAttachedToLogicalTree);
}
}
[Fact] [Fact]
public void ClientSize_Should_Be_Set_On_Construction() public void ClientSize_Should_Be_Set_On_Construction()
{ {

5
tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs

@ -144,6 +144,11 @@ namespace Avalonia.Styling.UnitTests
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
throw new NotImplementedException();
}
public void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) public void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

Loading…
Cancel
Save