committed by
GitHub
99 changed files with 2758 additions and 1577 deletions
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="System.Memory" Version="4.5.0" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -1,42 +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.Reactive; |
|||
using System.Reactive.Disposables; |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
/// <summary>
|
|||
/// An <see cref="IObservable{T}"/> with an additional description.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
|
|||
public class AvaloniaObservable<T> : ObservableBase<T>, IDescription |
|||
{ |
|||
private readonly Func<IObserver<T>, IDisposable> _subscribe; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AvaloniaObservable{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="subscribe">The subscribe function.</param>
|
|||
/// <param name="description">The description of the observable.</param>
|
|||
public AvaloniaObservable(Func<IObserver<T>, IDisposable> subscribe, string description) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(subscribe != null); |
|||
|
|||
_subscribe = subscribe; |
|||
Description = description; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the description of the observable.
|
|||
/// </summary>
|
|||
public string Description { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override IDisposable SubscribeCore(IObserver<T> observer) |
|||
{ |
|||
return _subscribe(observer) ?? Disposable.Empty; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
internal class AvaloniaPropertyChangedObservable : |
|||
LightweightObservableBase<AvaloniaPropertyChangedEventArgs>, |
|||
IDescription |
|||
{ |
|||
private readonly WeakReference<IAvaloniaObject> _target; |
|||
private readonly AvaloniaProperty _property; |
|||
|
|||
public AvaloniaPropertyChangedObservable( |
|||
IAvaloniaObject target, |
|||
AvaloniaProperty property) |
|||
{ |
|||
_target = new WeakReference<IAvaloniaObject>(target); |
|||
_property = property; |
|||
} |
|||
|
|||
public string Description => $"{_target.GetType().Name}.{_property.Name}"; |
|||
|
|||
protected override void Initialize() |
|||
{ |
|||
if (_target.TryGetTarget(out var target)) |
|||
{ |
|||
target.PropertyChanged += PropertyChanged; |
|||
} |
|||
} |
|||
|
|||
protected override void Deinitialize() |
|||
{ |
|||
if (_target.TryGetTarget(out var target)) |
|||
{ |
|||
target.PropertyChanged -= PropertyChanged; |
|||
} |
|||
} |
|||
|
|||
private void PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Property == _property) |
|||
{ |
|||
PublishNext(e); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
internal class AvaloniaPropertyObservable<T> : LightweightObservableBase<T>, IDescription |
|||
{ |
|||
private readonly WeakReference<IAvaloniaObject> _target; |
|||
private readonly AvaloniaProperty _property; |
|||
private T _value; |
|||
|
|||
public AvaloniaPropertyObservable( |
|||
IAvaloniaObject target, |
|||
AvaloniaProperty property) |
|||
{ |
|||
_target = new WeakReference<IAvaloniaObject>(target); |
|||
_property = property; |
|||
} |
|||
|
|||
public string Description => $"{_target.GetType().Name}.{_property.Name}"; |
|||
|
|||
protected override void Initialize() |
|||
{ |
|||
if (_target.TryGetTarget(out var target)) |
|||
{ |
|||
_value = (T)target.GetValue(_property); |
|||
target.PropertyChanged += PropertyChanged; |
|||
} |
|||
} |
|||
|
|||
protected override void Deinitialize() |
|||
{ |
|||
if (_target.TryGetTarget(out var target)) |
|||
{ |
|||
target.PropertyChanged -= PropertyChanged; |
|||
} |
|||
} |
|||
|
|||
protected override void Subscribed(IObserver<T> observer, bool first) |
|||
{ |
|||
observer.OnNext(_value); |
|||
} |
|||
|
|||
private void PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Property == _property) |
|||
{ |
|||
_value = (T)e.NewValue; |
|||
PublishNext(_value); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,202 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive; |
|||
using System.Reactive.Disposables; |
|||
using System.Threading; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
/// <summary>
|
|||
/// Lightweight base class for observable implementations.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The observable type.</typeparam>
|
|||
/// <remarks>
|
|||
/// <see cref="ObservableBase{T}"/> is rather heavyweight in terms of allocations and memory
|
|||
/// usage. This class provides a more lightweight base for some internal observable types
|
|||
/// in the Avalonia framework.
|
|||
/// </remarks>
|
|||
public abstract class LightweightObservableBase<T> : IObservable<T> |
|||
{ |
|||
private Exception _error; |
|||
private List<IObserver<T>> _observers = new List<IObserver<T>>(); |
|||
|
|||
public IDisposable Subscribe(IObserver<T> observer) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(observer != null); |
|||
Dispatcher.UIThread.VerifyAccess(); |
|||
|
|||
var first = false; |
|||
|
|||
for (; ; ) |
|||
{ |
|||
if (Volatile.Read(ref _observers) == null) |
|||
{ |
|||
if (_error != null) |
|||
{ |
|||
observer.OnError(_error); |
|||
} |
|||
else |
|||
{ |
|||
observer.OnCompleted(); |
|||
} |
|||
|
|||
return Disposable.Empty; |
|||
} |
|||
|
|||
lock (this) |
|||
{ |
|||
if (_observers == null) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
first = _observers.Count == 0; |
|||
_observers.Add(observer); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (first) |
|||
{ |
|||
Initialize(); |
|||
} |
|||
|
|||
Subscribed(observer, first); |
|||
|
|||
return new RemoveObserver(this, observer); |
|||
} |
|||
|
|||
void Remove(IObserver<T> observer) |
|||
{ |
|||
if (Volatile.Read(ref _observers) != null) |
|||
{ |
|||
lock (this) |
|||
{ |
|||
var observers = _observers; |
|||
|
|||
if (observers != null) |
|||
{ |
|||
observers.Remove(observer); |
|||
|
|||
if (observers.Count == 0) |
|||
{ |
|||
observers.TrimExcess(); |
|||
} |
|||
else |
|||
{ |
|||
return; |
|||
} |
|||
} else |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
Deinitialize(); |
|||
} |
|||
} |
|||
|
|||
sealed class RemoveObserver : IDisposable |
|||
{ |
|||
LightweightObservableBase<T> _parent; |
|||
|
|||
IObserver<T> _observer; |
|||
|
|||
public RemoveObserver(LightweightObservableBase<T> parent, IObserver<T> observer) |
|||
{ |
|||
_parent = parent; |
|||
Volatile.Write(ref _observer, observer); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
var observer = _observer; |
|||
Interlocked.Exchange(ref _parent, null)?.Remove(observer); |
|||
_observer = null; |
|||
} |
|||
} |
|||
|
|||
protected abstract void Initialize(); |
|||
protected abstract void Deinitialize(); |
|||
|
|||
protected void PublishNext(T value) |
|||
{ |
|||
if (Volatile.Read(ref _observers) != null) |
|||
{ |
|||
IObserver<T>[] observers; |
|||
|
|||
lock (this) |
|||
{ |
|||
if (_observers == null) |
|||
{ |
|||
return; |
|||
} |
|||
observers = _observers.ToArray(); |
|||
} |
|||
|
|||
foreach (var observer in observers) |
|||
{ |
|||
observer.OnNext(value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected void PublishCompleted() |
|||
{ |
|||
if (Volatile.Read(ref _observers) != null) |
|||
{ |
|||
IObserver<T>[] observers; |
|||
|
|||
lock (this) |
|||
{ |
|||
if (_observers == null) |
|||
{ |
|||
return; |
|||
} |
|||
observers = _observers.ToArray(); |
|||
Volatile.Write(ref _observers, null); |
|||
} |
|||
|
|||
foreach (var observer in observers) |
|||
{ |
|||
observer.OnCompleted(); |
|||
} |
|||
|
|||
Deinitialize(); |
|||
} |
|||
} |
|||
|
|||
protected void PublishError(Exception error) |
|||
{ |
|||
if (Volatile.Read(ref _observers) != null) |
|||
{ |
|||
|
|||
IObserver<T>[] observers; |
|||
|
|||
lock (this) |
|||
{ |
|||
if (_observers == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_error = error; |
|||
observers = _observers.ToArray(); |
|||
Volatile.Write(ref _observers, null); |
|||
} |
|||
|
|||
foreach (var observer in observers) |
|||
{ |
|||
observer.OnError(error); |
|||
} |
|||
|
|||
Deinitialize(); |
|||
} |
|||
} |
|||
|
|||
protected virtual void Subscribed(IObserver<T> observer, bool first) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
using System; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
public abstract class SingleSubscriberObservableBase<T> : IObservable<T>, IDisposable |
|||
{ |
|||
private Exception _error; |
|||
private IObserver<T> _observer; |
|||
private bool _completed; |
|||
|
|||
public IDisposable Subscribe(IObserver<T> observer) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(observer != null); |
|||
Dispatcher.UIThread.VerifyAccess(); |
|||
|
|||
if (_observer != null) |
|||
{ |
|||
throw new InvalidOperationException("The observable can only be subscribed once."); |
|||
} |
|||
|
|||
if (_error != null) |
|||
{ |
|||
observer.OnError(_error); |
|||
} |
|||
else if (_completed) |
|||
{ |
|||
observer.OnCompleted(); |
|||
} |
|||
else |
|||
{ |
|||
_observer = observer; |
|||
Subscribed(); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
void IDisposable.Dispose() |
|||
{ |
|||
Unsubscribed(); |
|||
_observer = null; |
|||
} |
|||
|
|||
protected abstract void Unsubscribed(); |
|||
|
|||
protected void PublishNext(T value) |
|||
{ |
|||
_observer?.OnNext(value); |
|||
} |
|||
|
|||
protected void PublishCompleted() |
|||
{ |
|||
if (_observer != null) |
|||
{ |
|||
_observer.OnCompleted(); |
|||
_completed = true; |
|||
Unsubscribed(); |
|||
_observer = null; |
|||
} |
|||
} |
|||
|
|||
protected void PublishError(Exception error) |
|||
{ |
|||
if (_observer != null) |
|||
{ |
|||
_observer.OnError(error); |
|||
_error = error; |
|||
Unsubscribed(); |
|||
_observer = null; |
|||
} |
|||
} |
|||
|
|||
protected abstract void Subscribed(); |
|||
} |
|||
} |
|||
@ -1,85 +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.Reactive; |
|||
using System.Reactive.Disposables; |
|||
using System.Reactive.Linq; |
|||
using System.Reactive.Subjects; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
internal class WeakPropertyChangedObservable : ObservableBase<object>, |
|||
IWeakSubscriber<AvaloniaPropertyChangedEventArgs>, IDescription |
|||
{ |
|||
private WeakReference<IAvaloniaObject> _sourceReference; |
|||
private readonly AvaloniaProperty _property; |
|||
private readonly Subject<object> _changed = new Subject<object>(); |
|||
|
|||
private int _count; |
|||
|
|||
public WeakPropertyChangedObservable( |
|||
WeakReference<IAvaloniaObject> source, |
|||
AvaloniaProperty property, |
|||
string description) |
|||
{ |
|||
_sourceReference = source; |
|||
_property = property; |
|||
Description = description; |
|||
} |
|||
|
|||
public string Description { get; } |
|||
|
|||
public void OnEvent(object sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Property == _property) |
|||
{ |
|||
_changed.OnNext(e.NewValue); |
|||
} |
|||
} |
|||
|
|||
protected override IDisposable SubscribeCore(IObserver<object> observer) |
|||
{ |
|||
IAvaloniaObject instance; |
|||
|
|||
if (_sourceReference.TryGetTarget(out instance)) |
|||
{ |
|||
if (_count++ == 0) |
|||
{ |
|||
WeakSubscriptionManager.Subscribe( |
|||
instance, |
|||
nameof(instance.PropertyChanged), |
|||
this); |
|||
} |
|||
|
|||
observer.OnNext(instance.GetValue(_property)); |
|||
|
|||
return Observable.Using(() => Disposable.Create(DecrementCount), _ => _changed) |
|||
.Subscribe(observer); |
|||
} |
|||
else |
|||
{ |
|||
_changed.OnCompleted(); |
|||
observer.OnCompleted(); |
|||
return Disposable.Empty; |
|||
} |
|||
} |
|||
|
|||
private void DecrementCount() |
|||
{ |
|||
if (--_count == 0) |
|||
{ |
|||
IAvaloniaObject instance; |
|||
|
|||
if (_sourceReference.TryGetTarget(out instance)) |
|||
{ |
|||
WeakSubscriptionManager.Unsubscribe( |
|||
instance, |
|||
nameof(instance.PropertyChanged), |
|||
this); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,172 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
internal class ValueStore : IPriorityValueOwner |
|||
{ |
|||
private readonly AvaloniaObject _owner; |
|||
private readonly Dictionary<AvaloniaProperty, object> _values = |
|||
new Dictionary<AvaloniaProperty, object>(); |
|||
|
|||
public ValueStore(AvaloniaObject owner) |
|||
{ |
|||
_owner = owner; |
|||
} |
|||
|
|||
public IDisposable AddBinding( |
|||
AvaloniaProperty property, |
|||
IObservable<object> source, |
|||
BindingPriority priority) |
|||
{ |
|||
PriorityValue priorityValue; |
|||
|
|||
if (_values.TryGetValue(property, out var v)) |
|||
{ |
|||
priorityValue = v as PriorityValue; |
|||
|
|||
if (priorityValue == null) |
|||
{ |
|||
priorityValue = CreatePriorityValue(property); |
|||
priorityValue.SetValue(v, (int)BindingPriority.LocalValue); |
|||
_values[property] = priorityValue; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
priorityValue = CreatePriorityValue(property); |
|||
_values.Add(property, priorityValue); |
|||
} |
|||
|
|||
return priorityValue.Add(source, (int)priority); |
|||
} |
|||
|
|||
public void AddValue(AvaloniaProperty property, object value, int priority) |
|||
{ |
|||
PriorityValue priorityValue; |
|||
|
|||
if (_values.TryGetValue(property, out var v)) |
|||
{ |
|||
priorityValue = v as PriorityValue; |
|||
|
|||
if (priorityValue == null) |
|||
{ |
|||
if (priority == (int)BindingPriority.LocalValue) |
|||
{ |
|||
_values[property] = Validate(property, value); |
|||
Changed(property, priority, v, value); |
|||
return; |
|||
} |
|||
else |
|||
{ |
|||
priorityValue = CreatePriorityValue(property); |
|||
priorityValue.SetValue(v, (int)BindingPriority.LocalValue); |
|||
_values[property] = priorityValue; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (value == AvaloniaProperty.UnsetValue) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (priority == (int)BindingPriority.LocalValue) |
|||
{ |
|||
_values.Add(property, Validate(property, value)); |
|||
Changed(property, priority, AvaloniaProperty.UnsetValue, value); |
|||
return; |
|||
} |
|||
else |
|||
{ |
|||
priorityValue = CreatePriorityValue(property); |
|||
_values.Add(property, priorityValue); |
|||
} |
|||
} |
|||
|
|||
priorityValue.SetValue(value, priority); |
|||
} |
|||
|
|||
public void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification) |
|||
{ |
|||
((IPriorityValueOwner)_owner).BindingNotificationReceived(property, notification); |
|||
} |
|||
|
|||
public void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue) |
|||
{ |
|||
((IPriorityValueOwner)_owner).Changed(property, priority, oldValue, newValue); |
|||
} |
|||
|
|||
public IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => throw new NotImplementedException(); |
|||
|
|||
public object GetValue(AvaloniaProperty property) |
|||
{ |
|||
var result = AvaloniaProperty.UnsetValue; |
|||
|
|||
if (_values.TryGetValue(property, out var value)) |
|||
{ |
|||
result = (value is PriorityValue priorityValue) ? priorityValue.Value : value; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public bool IsAnimating(AvaloniaProperty property) |
|||
{ |
|||
return _values.TryGetValue(property, out var value) ? (value as PriorityValue)?.IsAnimating ?? false : false; |
|||
} |
|||
|
|||
public bool IsSet(AvaloniaProperty property) |
|||
{ |
|||
if (_values.TryGetValue(property, out var value)) |
|||
{ |
|||
return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public void Revalidate(AvaloniaProperty property) |
|||
{ |
|||
if (_values.TryGetValue(property, out var value)) |
|||
{ |
|||
(value as PriorityValue)?.Revalidate(); |
|||
} |
|||
} |
|||
|
|||
public void VerifyAccess() => _owner.VerifyAccess(); |
|||
|
|||
private PriorityValue CreatePriorityValue(AvaloniaProperty property) |
|||
{ |
|||
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType()); |
|||
Func<object, object> validate2 = null; |
|||
|
|||
if (validate != null) |
|||
{ |
|||
validate2 = v => validate(_owner, v); |
|||
} |
|||
|
|||
PriorityValue result = new PriorityValue( |
|||
this, |
|||
property, |
|||
property.PropertyType, |
|||
validate2); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private object Validate(AvaloniaProperty property, object value) |
|||
{ |
|||
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType()); |
|||
|
|||
if (validate != null && value != AvaloniaProperty.UnsetValue) |
|||
{ |
|||
return validate(_owner, value); |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// 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.
|
|||
|
|||
namespace Avalonia |
|||
{ |
|||
/// <summary>
|
|||
/// Enum for ExitMode
|
|||
/// </summary>
|
|||
public enum ExitMode |
|||
{ |
|||
/// <summary>
|
|||
/// Indicates an implicit call to Application.Exit when the last window closes.
|
|||
/// </summary>
|
|||
OnLastWindowClose, |
|||
|
|||
/// <summary>
|
|||
/// Indicates an implicit call to Application.Exit when the main window closes.
|
|||
/// </summary>
|
|||
OnMainWindowClose, |
|||
|
|||
/// <summary>
|
|||
/// Indicates that the application only exits on an explicit call to Application.Exit.
|
|||
/// </summary>
|
|||
OnExplicitExit |
|||
} |
|||
} |
|||
@ -0,0 +1,134 @@ |
|||
// 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.Collections; |
|||
using System.Collections.Generic; |
|||
|
|||
using Avalonia.Controls; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
public class WindowCollection : IReadOnlyList<Window> |
|||
{ |
|||
private readonly Application _application; |
|||
private readonly List<Window> _windows = new List<Window>(); |
|||
|
|||
public WindowCollection(Application application) |
|||
{ |
|||
_application = application; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
/// <summary>
|
|||
/// Gets the number of elements in the collection.
|
|||
/// </summary>
|
|||
public int Count => _windows.Count; |
|||
|
|||
/// <inheritdoc />
|
|||
/// <summary>
|
|||
/// Gets the <see cref="T:Avalonia.Controls.Window" /> at the specified index.
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The <see cref="T:Avalonia.Controls.Window" />.
|
|||
/// </value>
|
|||
/// <param name="index">The index.</param>
|
|||
/// <returns></returns>
|
|||
public Window this[int index] => _windows[index]; |
|||
|
|||
/// <inheritdoc />
|
|||
/// <summary>
|
|||
/// Returns an enumerator that iterates through the collection.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// An enumerator that can be used to iterate through the collection.
|
|||
/// </returns>
|
|||
public IEnumerator<Window> GetEnumerator() |
|||
{ |
|||
return _windows.GetEnumerator(); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
/// <summary>
|
|||
/// Returns an enumerator that iterates through a collection.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
|
|||
/// </returns>
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return GetEnumerator(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds the specified window.
|
|||
/// </summary>
|
|||
/// <param name="window">The window.</param>
|
|||
internal void Add(Window window) |
|||
{ |
|||
if (window == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_windows.Add(window); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the specified window.
|
|||
/// </summary>
|
|||
/// <param name="window">The window.</param>
|
|||
internal void Remove(Window window) |
|||
{ |
|||
if (window == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_windows.Remove(window); |
|||
|
|||
OnRemoveWindow(window); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Closes all windows and removes them from the underlying collection.
|
|||
/// </summary>
|
|||
internal void Clear() |
|||
{ |
|||
while (_windows.Count > 0) |
|||
{ |
|||
_windows[0].Close(); |
|||
} |
|||
} |
|||
|
|||
private void OnRemoveWindow(Window window) |
|||
{ |
|||
if (window == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (_application.IsExiting) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
switch (_application.ExitMode) |
|||
{ |
|||
case ExitMode.OnLastWindowClose: |
|||
if (Count == 0) |
|||
{ |
|||
_application.Exit(); |
|||
} |
|||
|
|||
break; |
|||
case ExitMode.OnMainWindowClose: |
|||
if (window == _application.MainWindow) |
|||
{ |
|||
_application.Exit(); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,51 +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.Data; |
|||
|
|||
namespace Avalonia.Markup.Xaml.MarkupExtensions |
|||
{ |
|||
using System; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Markup.Data; |
|||
using Portable.Xaml.Markup; |
|||
|
|||
[MarkupExtensionReturnType(typeof(IBinding))] |
|||
public class TemplateBindingExtension : MarkupExtension |
|||
{ |
|||
public TemplateBindingExtension() |
|||
{ |
|||
} |
|||
|
|||
public TemplateBindingExtension(string path) |
|||
{ |
|||
Path = path; |
|||
} |
|||
|
|||
public override object ProvideValue(IServiceProvider serviceProvider) |
|||
{ |
|||
return new Binding |
|||
{ |
|||
Converter = Converter, |
|||
ElementName = ElementName, |
|||
Mode = Mode, |
|||
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), |
|||
Path = Path ?? string.Empty, |
|||
Priority = Priority, |
|||
}; |
|||
} |
|||
|
|||
public IValueConverter Converter { get; set; } |
|||
|
|||
public string ElementName { get; set; } |
|||
|
|||
public object FallbackValue { get; set; } |
|||
|
|||
public BindingMode Mode { get; set; } |
|||
|
|||
[ConstructorArgument("path")] |
|||
public string Path { get; set; } |
|||
|
|||
public BindingPriority Priority { get; set; } = BindingPriority.TemplatedParent; |
|||
} |
|||
} |
|||
@ -0,0 +1,178 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.Reactive.Subjects; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Data |
|||
{ |
|||
/// <summary>
|
|||
/// A XAML binding to a property on a control's templated parent.
|
|||
/// </summary>
|
|||
public class TemplateBinding : SingleSubscriberObservableBase<object>, |
|||
IBinding, |
|||
IDescription, |
|||
ISubject<object> |
|||
{ |
|||
private IStyledElement _target; |
|||
private Type _targetType; |
|||
|
|||
public TemplateBinding() |
|||
{ |
|||
} |
|||
|
|||
public TemplateBinding(AvaloniaProperty property) |
|||
{ |
|||
Property = property; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public InstancedBinding Initiate( |
|||
IAvaloniaObject target, |
|||
AvaloniaProperty targetProperty, |
|||
object anchor = null, |
|||
bool enableDataValidation = false) |
|||
{ |
|||
// Usually each `TemplateBinding` will only be instantiated once; in this case we can
|
|||
// use the `TemplateBinding` object itself as the instanced binding in order to save
|
|||
// allocating a new object. If the binding *is* instantiated more than once (which can
|
|||
// happen if it appears in a `Setter` for example, then just make a clone and instantiate
|
|||
// that.
|
|||
if (_target == null) |
|||
{ |
|||
_target = (IStyledElement)target; |
|||
_targetType = targetProperty?.PropertyType; |
|||
|
|||
return new InstancedBinding( |
|||
this, |
|||
Mode == BindingMode.Default ? BindingMode.OneWay : Mode, |
|||
BindingPriority.TemplatedParent); |
|||
} |
|||
else |
|||
{ |
|||
var clone = new TemplateBinding |
|||
{ |
|||
Converter = Converter, |
|||
ConverterParameter = ConverterParameter, |
|||
Property = Property, |
|||
}; |
|||
|
|||
return clone.Initiate(target, targetProperty, anchor, enableDataValidation); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="IValueConverter"/> to use.
|
|||
/// </summary>
|
|||
public IValueConverter Converter { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
|
|||
/// </summary>
|
|||
public object ConverterParameter { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the binding mode.
|
|||
/// </summary>
|
|||
public BindingMode Mode { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the name of the source property on the templated parent.
|
|||
/// </summary>
|
|||
public AvaloniaProperty Property { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public string Description => "TemplateBinding: " + Property; |
|||
|
|||
void IObserver<object>.OnCompleted() => throw new NotImplementedException(); |
|||
void IObserver<object>.OnError(Exception error) => throw new NotImplementedException(); |
|||
|
|||
void IObserver<object>.OnNext(object value) |
|||
{ |
|||
if (_target.TemplatedParent != null && Property != null) |
|||
{ |
|||
if (Converter != null) |
|||
{ |
|||
value = Converter.ConvertBack( |
|||
value, |
|||
Property.PropertyType, |
|||
ConverterParameter, |
|||
CultureInfo.CurrentCulture); |
|||
} |
|||
|
|||
_target.TemplatedParent.SetValue(Property, value, BindingPriority.TemplatedParent); |
|||
} |
|||
} |
|||
|
|||
protected override void Subscribed() |
|||
{ |
|||
TemplatedParentChanged(); |
|||
_target.PropertyChanged += TargetPropertyChanged; |
|||
} |
|||
|
|||
protected override void Unsubscribed() |
|||
{ |
|||
if (_target.TemplatedParent != null) |
|||
{ |
|||
_target.TemplatedParent.PropertyChanged -= TemplatedParentPropertyChanged; |
|||
} |
|||
|
|||
_target.PropertyChanged -= TargetPropertyChanged; |
|||
} |
|||
|
|||
private void PublishValue() |
|||
{ |
|||
if (_target.TemplatedParent != null) |
|||
{ |
|||
var value = Property != null ? |
|||
_target.TemplatedParent.GetValue(Property) : |
|||
_target.TemplatedParent; |
|||
|
|||
if (Converter != null) |
|||
{ |
|||
value = Converter.Convert(value, _targetType, ConverterParameter, CultureInfo.CurrentCulture); |
|||
} |
|||
|
|||
PublishNext(value); |
|||
} |
|||
else |
|||
{ |
|||
PublishNext(AvaloniaProperty.UnsetValue); |
|||
} |
|||
} |
|||
|
|||
private void TemplatedParentChanged() |
|||
{ |
|||
if (_target.TemplatedParent != null) |
|||
{ |
|||
_target.TemplatedParent.PropertyChanged += TemplatedParentPropertyChanged; |
|||
} |
|||
|
|||
PublishValue(); |
|||
} |
|||
|
|||
private void TargetPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Property == StyledElement.TemplatedParentProperty) |
|||
{ |
|||
var oldValue = (IAvaloniaObject)e.OldValue; |
|||
var newValue = (IAvaloniaObject)e.OldValue; |
|||
|
|||
if (oldValue != null) |
|||
{ |
|||
oldValue.PropertyChanged -= TemplatedParentPropertyChanged; |
|||
} |
|||
|
|||
TemplatedParentChanged(); |
|||
} |
|||
} |
|||
|
|||
private void TemplatedParentPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Property == Property) |
|||
{ |
|||
PublishNext(_target.TemplatedParent.GetValue(Property)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
// 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 Avalonia.UnitTests; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests |
|||
{ |
|||
public class ApplicationTests |
|||
{ |
|||
[Fact] |
|||
public void Should_Exit_After_MainWindow_Closed() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
|||
{ |
|||
Application.Current.ExitMode = ExitMode.OnMainWindowClose; |
|||
|
|||
var mainWindow = new Window(); |
|||
|
|||
mainWindow.Show(); |
|||
|
|||
Application.Current.MainWindow = mainWindow; |
|||
|
|||
var window = new Window(); |
|||
|
|||
window.Show(); |
|||
|
|||
mainWindow.Close(); |
|||
|
|||
Assert.True(Application.Current.IsExiting); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Exit_After_Last_Window_Closed() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
|||
{ |
|||
Application.Current.ExitMode = ExitMode.OnLastWindowClose; |
|||
|
|||
var windowA = new Window(); |
|||
|
|||
windowA.Show(); |
|||
|
|||
var windowB = new Window(); |
|||
|
|||
windowB.Show(); |
|||
|
|||
windowA.Close(); |
|||
|
|||
Assert.False(Application.Current.IsExiting); |
|||
|
|||
windowB.Close(); |
|||
|
|||
Assert.True(Application.Current.IsExiting); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Only_Exit_On_Explicit_Exit() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
|||
{ |
|||
Application.Current.ExitMode = ExitMode.OnExplicitExit; |
|||
|
|||
var windowA = new Window(); |
|||
|
|||
windowA.Show(); |
|||
|
|||
var windowB = new Window(); |
|||
|
|||
windowB.Show(); |
|||
|
|||
windowA.Close(); |
|||
|
|||
Assert.False(Application.Current.IsExiting); |
|||
|
|||
windowB.Close(); |
|||
|
|||
Assert.False(Application.Current.IsExiting); |
|||
|
|||
Application.Current.Exit(); |
|||
|
|||
Assert.True(Application.Current.IsExiting); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Close_All_Remaining_Open_Windows_After_Explicit_Exit_Call() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
|||
{ |
|||
var windows = new List<Window> { new Window(), new Window(), new Window(), new Window() }; |
|||
|
|||
foreach (var window in windows) |
|||
{ |
|||
window.Show(); |
|||
} |
|||
|
|||
Application.Current.Exit(); |
|||
|
|||
Assert.Empty(Application.Current.Windows); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Throws_ArgumentNullException_On_Run_If_MainWindow_Is_Null() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
|||
{ |
|||
Assert.Throws<ArgumentNullException>(() => { Application.Current.Run(null); }); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue