committed by
GitHub
103 changed files with 1048 additions and 1042 deletions
@ -0,0 +1,106 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace Avalonia.Utilities |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A task-like operation that is guaranteed to finish continuations synchronously,
|
||||
|
/// can be used for parametrized one-shot events
|
||||
|
/// </summary>
|
||||
|
public struct SynchronousCompletionAsyncResult<T> : INotifyCompletion |
||||
|
{ |
||||
|
private readonly SynchronousCompletionAsyncResultSource<T> _source; |
||||
|
private readonly T _result; |
||||
|
private readonly bool _isValid; |
||||
|
internal SynchronousCompletionAsyncResult(SynchronousCompletionAsyncResultSource<T> source) |
||||
|
{ |
||||
|
_source = source; |
||||
|
_result = default; |
||||
|
_isValid = true; |
||||
|
} |
||||
|
|
||||
|
public SynchronousCompletionAsyncResult(T result) |
||||
|
{ |
||||
|
_result = result; |
||||
|
_source = null; |
||||
|
_isValid = true; |
||||
|
} |
||||
|
|
||||
|
static void ThrowNotInitialized() => |
||||
|
throw new InvalidOperationException("This SynchronousCompletionAsyncResult was not initialized"); |
||||
|
|
||||
|
public bool IsCompleted |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (!_isValid) |
||||
|
ThrowNotInitialized(); |
||||
|
return _source == null || _source.IsCompleted; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public T GetResult() |
||||
|
{ |
||||
|
if (!_isValid) |
||||
|
ThrowNotInitialized(); |
||||
|
return _source == null ? _result : _source.Result; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public void OnCompleted(Action continuation) |
||||
|
{ |
||||
|
if (!_isValid) |
||||
|
ThrowNotInitialized(); |
||||
|
if (_source == null) |
||||
|
continuation(); |
||||
|
else |
||||
|
_source.OnCompleted(continuation); |
||||
|
} |
||||
|
|
||||
|
public SynchronousCompletionAsyncResult<T> GetAwaiter() => this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Source for incomplete SynchronousCompletionAsyncResult
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T"></typeparam>
|
||||
|
public class SynchronousCompletionAsyncResultSource<T> |
||||
|
{ |
||||
|
private T _result; |
||||
|
internal bool IsCompleted { get; private set; } |
||||
|
public SynchronousCompletionAsyncResult<T> AsyncResult => new SynchronousCompletionAsyncResult<T>(this); |
||||
|
|
||||
|
internal T Result => IsCompleted ? |
||||
|
_result : |
||||
|
throw new InvalidOperationException("Asynchronous operation is not yet completed"); |
||||
|
|
||||
|
private List<Action> _continuations; |
||||
|
|
||||
|
internal void OnCompleted(Action continuation) |
||||
|
{ |
||||
|
if(_continuations==null) |
||||
|
_continuations = new List<Action>(); |
||||
|
_continuations.Add(continuation); |
||||
|
} |
||||
|
|
||||
|
public void SetResult(T result) |
||||
|
{ |
||||
|
if (IsCompleted) |
||||
|
throw new InvalidOperationException("Asynchronous operation is already completed"); |
||||
|
_result = result; |
||||
|
IsCompleted = true; |
||||
|
if(_continuations!=null) |
||||
|
foreach (var c in _continuations) |
||||
|
c(); |
||||
|
_continuations = null; |
||||
|
} |
||||
|
|
||||
|
public void TrySetResult(T result) |
||||
|
{ |
||||
|
if(IsCompleted) |
||||
|
return; |
||||
|
SetResult(result); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Controls.Templates |
||||
|
{ |
||||
|
public static class FuncTemplateNameScopeExtensions |
||||
|
{ |
||||
|
public static T RegisterInNameScope<T>(this T control, INameScope scope) |
||||
|
where T : StyledElement |
||||
|
{ |
||||
|
scope.Register(control.Name, control); |
||||
|
return control; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
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 SynchronousCompletionAsyncResult<object> FindAsync(string name) |
||||
|
{ |
||||
|
var found = Find(name); |
||||
|
if (found != null) |
||||
|
return new SynchronousCompletionAsyncResult<object>(found); |
||||
|
// Not found and both current and parent scope are in completed state
|
||||
|
if(IsCompleted) |
||||
|
return new SynchronousCompletionAsyncResult<object>(null); |
||||
|
return DoFindAsync(name); |
||||
|
} |
||||
|
|
||||
|
public SynchronousCompletionAsyncResult<object> DoFindAsync(string name) |
||||
|
{ |
||||
|
var src = new SynchronousCompletionAsyncResultSource<object>(); |
||||
|
|
||||
|
void ParentSearch() |
||||
|
{ |
||||
|
var parentSearch = _parentScope.FindAsync(name); |
||||
|
if (parentSearch.IsCompleted) |
||||
|
src.SetResult(parentSearch.GetResult()); |
||||
|
else |
||||
|
parentSearch.OnCompleted(() => src.SetResult(parentSearch.GetResult())); |
||||
|
} |
||||
|
if (!_inner.IsCompleted) |
||||
|
{ |
||||
|
// Guaranteed to be incomplete at this point
|
||||
|
var innerSearch = _inner.FindAsync(name); |
||||
|
innerSearch.OnCompleted(() => |
||||
|
{ |
||||
|
var value = innerSearch.GetResult(); |
||||
|
if (value != null) |
||||
|
src.SetResult(value); |
||||
|
else ParentSearch(); |
||||
|
}); |
||||
|
} |
||||
|
else |
||||
|
ParentSearch(); |
||||
|
|
||||
|
return src.AsyncResult; |
||||
|
} |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Reactive.Disposables; |
||||
|
using System.Reactive.Threading.Tasks; |
||||
|
using System.Reflection; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.LogicalTree; |
||||
|
using Avalonia.Reactive; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
public class NameScopeLocator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Tracks a named control relative to another control.
|
||||
|
/// </summary>
|
||||
|
/// <param name="relativeTo">
|
||||
|
/// The control relative from which the other control should be found.
|
||||
|
/// </param>
|
||||
|
/// <param name="name">The name of the control to find.</param>
|
||||
|
public static IObservable<object> Track(INameScope scope, string name) |
||||
|
{ |
||||
|
return new NeverEndingSynchronousCompletionAsyncResultObservable<object>(scope.FindAsync(name)); |
||||
|
} |
||||
|
|
||||
|
// This class is implemented in such weird way because for some reason
|
||||
|
// our binding system doesn't expect OnCompleted to be ever called and
|
||||
|
// seems to treat it as binding cancellation or something
|
||||
|
|
||||
|
private class NeverEndingSynchronousCompletionAsyncResultObservable<T> : IObservable<T> |
||||
|
{ |
||||
|
private T _value; |
||||
|
private SynchronousCompletionAsyncResult<T>? _asyncResult; |
||||
|
|
||||
|
public NeverEndingSynchronousCompletionAsyncResultObservable(SynchronousCompletionAsyncResult<T> task) |
||||
|
{ |
||||
|
if (task.IsCompleted) |
||||
|
_value = task.GetResult(); |
||||
|
else |
||||
|
_asyncResult = task; |
||||
|
} |
||||
|
|
||||
|
public IDisposable Subscribe(IObserver<T> observer) |
||||
|
{ |
||||
|
if (_asyncResult?.IsCompleted == true) |
||||
|
{ |
||||
|
_value = _asyncResult.Value.GetResult(); |
||||
|
_asyncResult = null; |
||||
|
} |
||||
|
|
||||
|
if (_asyncResult != null) |
||||
|
_asyncResult.Value.OnCompleted(() => |
||||
|
{ |
||||
|
observer.OnNext(_asyncResult.Value.GetResult()); |
||||
|
}); |
||||
|
else |
||||
|
observer.OnNext(_value); |
||||
|
|
||||
|
return Disposable.Empty; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1 +1 @@ |
|||||
Subproject commit 894b2c02827fd5eb16a338de5d5b6c9fbc60fef5 |
Subproject commit c2ec091f79fb4e1eea629bc823c9c24da7050022 |
||||
@ -1,155 +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 locator = ControlLocator.Track(relativeTo, "target"); |
|
||||
var result = await locator.Take(1); |
|
||||
|
|
||||
Assert.Same(target, result); |
|
||||
Assert.Equal(0, root.NameScopeRegisteredSubscribers); |
|
||||
Assert.Equal(0, root.NameScopeUnregisteredSubscribers); |
|
||||
} |
|
||||
|
|
||||
[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 locator = ControlLocator.Track(relativeTo, "target"); |
|
||||
var target = new TextBlock { Name = "target" }; |
|
||||
var result = new List<ILogical>(); |
|
||||
|
|
||||
using (locator.Subscribe(x => result.Add(x))) |
|
||||
{ |
|
||||
panel.Children.Add(target); |
|
||||
} |
|
||||
|
|
||||
Assert.Equal(new[] { null, target }, result); |
|
||||
Assert.Equal(0, root.NameScopeRegisteredSubscribers); |
|
||||
Assert.Equal(0, root.NameScopeUnregisteredSubscribers); |
|
||||
} |
|
||||
|
|
||||
[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 locator = ControlLocator.Track(relativeTo, "target"); |
|
||||
var result = new List<ILogical>(); |
|
||||
locator.Subscribe(x => result.Add(x)); |
|
||||
|
|
||||
var other = new TextBlock { Name = "target" }; |
|
||||
panel.Children.Remove(target); |
|
||||
panel.Children.Add(other); |
|
||||
|
|
||||
Assert.Equal(new[] { target, null, other }, result); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
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 root2 = new TestRoot |
|
||||
{ |
|
||||
Child = new StackPanel |
|
||||
{ |
|
||||
Children = |
|
||||
{ |
|
||||
(target2 = new TextBlock { Name = "target" }), |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
var locator = ControlLocator.Track(relativeTo, "target"); |
|
||||
var target = new TextBlock { Name = "target" }; |
|
||||
var result = new List<ILogical>(); |
|
||||
|
|
||||
using (locator.Subscribe(x => result.Add(x))) |
|
||||
{ |
|
||||
((StackPanel)root1.Child).Children.Remove(relativeTo); |
|
||||
((StackPanel)root2.Child).Children.Add(relativeTo); |
|
||||
} |
|
||||
|
|
||||
Assert.Equal(new[] { target1, null, target2 }, result); |
|
||||
Assert.Equal(0, root1.NameScopeRegisteredSubscribers); |
|
||||
Assert.Equal(0, root1.NameScopeUnregisteredSubscribers); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,69 +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 Avalonia.UnitTests; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Avalonia.Controls.UnitTests |
|
||||
{ |
|
||||
public class StyledElementTests_NameScope |
|
||||
{ |
|
||||
[Fact] |
|
||||
public void Controls_Should_Register_With_NameScope() |
|
||||
{ |
|
||||
var root = new TestRoot |
|
||||
{ |
|
||||
Child = new Border |
|
||||
{ |
|
||||
Name = "foo", |
|
||||
Child = new Border |
|
||||
{ |
|
||||
Name = "bar", |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
root.ApplyTemplate(); |
|
||||
|
|
||||
Assert.Same(root.FindControl<Border>("foo"), root.Child); |
|
||||
Assert.Same(root.FindControl<Border>("bar"), ((Border)root.Child).Child); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Control_Should_Unregister_With_NameScope() |
|
||||
{ |
|
||||
var root = new TestRoot |
|
||||
{ |
|
||||
Child = new Border |
|
||||
{ |
|
||||
Name = "foo", |
|
||||
Child = new Border |
|
||||
{ |
|
||||
Name = "bar", |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
root.Child = null; |
|
||||
|
|
||||
Assert.Null(root.FindControl<Border>("foo")); |
|
||||
Assert.Null(root.FindControl<Border>("bar")); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Control_Should_Not_Register_With_Template_NameScope() |
|
||||
{ |
|
||||
var root = new TestTemplatedRoot |
|
||||
{ |
|
||||
Content = new Border |
|
||||
{ |
|
||||
Name = "foo", |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
root.ApplyTemplate(); |
|
||||
|
|
||||
Assert.Null(NameScope.GetNameScope((StyledElement)root.Presenter).Find("foo")); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue