11 changed files with 275 additions and 366 deletions
@ -1,5 +1,6 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> |
|||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
</Project> |
|||
|
|||
@ -0,0 +1,54 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class ChildNameScope : INameScope |
|||
{ |
|||
private readonly INameScope _parentScope; |
|||
private readonly NameScope _inner = new NameScope(); |
|||
|
|||
public ChildNameScope(INameScope parentScope) |
|||
{ |
|||
_parentScope = parentScope; |
|||
} |
|||
|
|||
public void Register(string name, object element) => _inner.Register(name, element); |
|||
|
|||
public ValueTask<object> FindAsync(string name) |
|||
{ |
|||
var found = Find(name); |
|||
if (found != null) |
|||
return new ValueTask<object>(found); |
|||
// Not found and both current and parent scope are in completed stage
|
|||
if(IsCompleted) |
|||
return new ValueTask<object>(null); |
|||
return DoFindAsync(name); |
|||
} |
|||
|
|||
async ValueTask<object> DoFindAsync(string name) |
|||
{ |
|||
if (!_inner.IsCompleted) |
|||
{ |
|||
var found = await _inner.FindAsync(name); |
|||
if (found != null) |
|||
return found; |
|||
} |
|||
|
|||
return await _parentScope.FindAsync(name); |
|||
} |
|||
|
|||
public object Find(string name) |
|||
{ |
|||
var found = _inner.Find(name); |
|||
if (found != null) |
|||
return found; |
|||
if (_inner.IsCompleted) |
|||
return _parentScope.Find(name); |
|||
return null; |
|||
} |
|||
|
|||
public void Complete() => _inner.Complete(); |
|||
|
|||
public bool IsCompleted => _inner.IsCompleted && _parentScope.IsCompleted; |
|||
} |
|||
} |
|||
@ -1,221 +0,0 @@ |
|||
// 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 System.Reactive.Linq; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls; |
|||
using Avalonia.LogicalTree; |
|||
using Avalonia.UnitTests; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Styling.UnitTests |
|||
{ |
|||
public class ControlLocatorTests |
|||
{ |
|||
[Fact] |
|||
public async Task Track_By_Name_Should_Find_Control_Added_Earlier() |
|||
{ |
|||
TextBlock target; |
|||
TextBlock relativeTo; |
|||
|
|||
var root = new TestRoot |
|||
{ |
|||
Child = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
(target = new TextBlock { Name = "target" }), |
|||
(relativeTo = new TextBlock { Name = "start" }), |
|||
} |
|||
} |
|||
}; |
|||
var scope = Register(root, relativeTo); |
|||
Register(root, target); |
|||
|
|||
var locator = NameScopeLocator.Track(scope, "target"); |
|||
var result = await locator.Take(1); |
|||
|
|||
Assert.Same(target, result); |
|||
Assert.Equal(0, scope.RegisteredSubscribers); |
|||
Assert.Equal(0, scope.UnregisteredSubscribers); |
|||
} |
|||
|
|||
|
|||
|
|||
[Fact] |
|||
public void Track_By_Name_Should_Find_Control_Added_Later() |
|||
{ |
|||
StackPanel panel; |
|||
TextBlock relativeTo; |
|||
|
|||
var root = new TestRoot |
|||
{ |
|||
Child = (panel = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
(relativeTo = new TextBlock |
|||
{ |
|||
Name = "start" |
|||
}), |
|||
} |
|||
}) |
|||
}; |
|||
var scope = Register(root, relativeTo); |
|||
|
|||
var locator = NameScopeLocator.Track(scope, "target"); |
|||
var target = new TextBlock { Name = "target" }; |
|||
var result = new List<ILogical>(); |
|||
|
|||
using (locator.Subscribe(x => result.Add((ILogical)x))) |
|||
{ |
|||
panel.Children.Add(target); |
|||
Register(root, target); |
|||
} |
|||
|
|||
Assert.Equal(new[] { null, target }, result); |
|||
Assert.Equal(0, scope.RegisteredSubscribers); |
|||
Assert.Equal(0, scope.UnregisteredSubscribers); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Track_By_Name_Should_Track_Removal_And_Readd() |
|||
{ |
|||
StackPanel panel; |
|||
TextBlock target; |
|||
TextBlock relativeTo; |
|||
|
|||
var root = new TestRoot |
|||
{ |
|||
Child = panel = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
(target = new TextBlock { Name = "target" }), |
|||
(relativeTo = new TextBlock { Name = "start" }), |
|||
} |
|||
} |
|||
}; |
|||
var scope = Register(root, target); |
|||
Register(root, relativeTo); |
|||
|
|||
var locator = NameScopeLocator.Track(scope, "target"); |
|||
var result = new List<ILogical>(); |
|||
locator.Subscribe(x => result.Add((IControl)x)); |
|||
|
|||
var other = new TextBlock { Name = "target" }; |
|||
panel.Children.Remove(target); |
|||
scope.Unregister(target.Name); |
|||
panel.Children.Add(other); |
|||
Register(root, other); |
|||
|
|||
Assert.Equal(new[] { target, null, other }, result); |
|||
} |
|||
|
|||
[Fact(Skip = "I'm going to remove that logic anyway")] |
|||
public void Track_By_Name_Should_Find_Control_When_Tree_Changed() |
|||
{ |
|||
TextBlock target1; |
|||
TextBlock target2; |
|||
TextBlock relativeTo; |
|||
|
|||
var root1 = new TestRoot |
|||
{ |
|||
Child = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
(relativeTo = new TextBlock |
|||
{ |
|||
Name = "start" |
|||
}), |
|||
(target1 = new TextBlock { Name = "target" }), |
|||
} |
|||
} |
|||
}; |
|||
var scope1 = Register(root1, relativeTo); |
|||
Register(root1, relativeTo); |
|||
Register(root1, target1); |
|||
|
|||
var root2 = new TestRoot |
|||
{ |
|||
Child = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
(target2 = new TextBlock { Name = "target" }), |
|||
} |
|||
} |
|||
}; |
|||
var scope2 = Register(root2, target2); |
|||
|
|||
var locator = NameScopeLocator.Track(scope1, "target"); |
|||
var result = new List<ILogical>(); |
|||
|
|||
using (locator.Subscribe(x => result.Add((ILogical)x))) |
|||
{ |
|||
((StackPanel)root1.Child).Children.Remove(relativeTo); |
|||
scope1.Unregister(relativeTo.Name); |
|||
((StackPanel)root2.Child).Children.Add(relativeTo); |
|||
Register(root2, relativeTo); |
|||
} |
|||
|
|||
Assert.Equal(new[] { target1, null, target2 }, result); |
|||
Assert.Equal(0, scope1.RegisteredSubscribers); |
|||
Assert.Equal(0, scope1.UnregisteredSubscribers); |
|||
Assert.Equal(0, scope2.RegisteredSubscribers); |
|||
Assert.Equal(0, scope2.UnregisteredSubscribers); |
|||
} |
|||
|
|||
TrackingNameScope Register(StyledElement anchor, StyledElement element) |
|||
{ |
|||
var scope = (TrackingNameScope)NameScope.GetNameScope(anchor); |
|||
if (scope == null) |
|||
NameScope.SetNameScope(anchor, scope = new TrackingNameScope()); |
|||
scope.Register(element.Name, element); |
|||
return scope; |
|||
} |
|||
|
|||
class TrackingNameScope : INameScope |
|||
{ |
|||
public int RegisteredSubscribers { get; private set; } |
|||
public int UnregisteredSubscribers { get; private set; } |
|||
private NameScope _inner = new NameScope(); |
|||
public event EventHandler<NameScopeEventArgs> Registered |
|||
{ |
|||
add |
|||
{ |
|||
_inner.Registered += value; |
|||
RegisteredSubscribers++; |
|||
} |
|||
remove |
|||
{ |
|||
_inner.Registered -= value; |
|||
RegisteredSubscribers--; |
|||
} |
|||
} |
|||
|
|||
public event EventHandler<NameScopeEventArgs> Unregistered |
|||
{ |
|||
add |
|||
{ |
|||
_inner.Unregistered += value; |
|||
UnregisteredSubscribers++; |
|||
} |
|||
remove |
|||
{ |
|||
_inner.Unregistered -= value; |
|||
UnregisteredSubscribers--; |
|||
} |
|||
} |
|||
|
|||
public void Register(string name, object element) => _inner.Register(name, element); |
|||
|
|||
public object Find(string name) => _inner.Find(name); |
|||
|
|||
public void Unregister(string name) => _inner.Unregister(name); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue