// 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.Disposables; using Avalonia.Data; namespace Avalonia { /// /// Stores bindings for a priority level in a . /// /// /// /// Each priority level in a has a current , /// a list of and a . When there are no /// bindings present, or all bindings return then /// Value will equal DirectValue. /// /// /// When there are bindings present, then the latest added binding that doesn't return /// UnsetValue will take precedence. The active binding is returned by the /// property (which refers to the active binding's /// property rather than the index in /// Bindings). /// /// /// If DirectValue is set while a binding is active, then it will replace the /// current value until the active binding fires again. /// /// internal class PriorityLevel { private object _directValue; private int _nextIndex; /// /// Initializes a new instance of the class. /// /// The owner. /// The priority. public PriorityLevel( PriorityValue owner, int priority) { Contract.Requires(owner != null); Owner = owner; Priority = priority; Value = _directValue = AvaloniaProperty.UnsetValue; ActiveBindingIndex = -1; Bindings = new LinkedList(); } /// /// Gets the owner of the level. /// public PriorityValue Owner { get; } /// /// Gets the priority of this level. /// public int Priority { get; } /// /// Gets or sets the direct value for this priority level. /// public object DirectValue { get { return _directValue; } set { Value = _directValue = value; Owner.LevelValueChanged(this); } } /// /// Gets the current binding for the priority level. /// public object Value { get; private set; } /// /// Gets the value of the active binding, or -1 /// if no binding is active. /// public int ActiveBindingIndex { get; private set; } /// /// Gets the bindings for the priority level. /// public LinkedList Bindings { get; } /// /// Adds a binding. /// /// The binding to add. /// A disposable used to remove the binding. public IDisposable Add(IObservable binding) { Contract.Requires(binding != null); var entry = new PriorityBindingEntry(this, _nextIndex++); var node = Bindings.AddFirst(entry); entry.Start(binding); return Disposable.Create(() => { if (!entry.HasCompleted) { Bindings.Remove(node); entry.Dispose(); if (entry.Index >= ActiveBindingIndex) { ActivateFirstBinding(); } } }); } /// /// Invoked when an entry in changes value. /// /// The entry that changed. public void Changed(PriorityBindingEntry entry) { if (entry.Index >= ActiveBindingIndex) { if (entry.Value != AvaloniaProperty.UnsetValue) { Value = entry.Value; ActiveBindingIndex = entry.Index; Owner.LevelValueChanged(this); } else { ActivateFirstBinding(); } } } /// /// Invoked when an entry in completes. /// /// The entry that completed. public void Completed(PriorityBindingEntry entry) { Bindings.Remove(entry); if (entry.Index >= ActiveBindingIndex) { ActivateFirstBinding(); } } /// /// Invoked when an entry in encounters a recoverable error. /// /// The entry that completed. /// The error. public void Error(PriorityBindingEntry entry, BindingNotification error) { Owner.LevelError(this, error); } /// /// Activates the first binding that has a value. /// private void ActivateFirstBinding() { foreach (var binding in Bindings) { if (binding.Value != AvaloniaProperty.UnsetValue) { Value = binding.Value; ActiveBindingIndex = binding.Index; Owner.LevelValueChanged(this); return; } } Value = DirectValue; ActiveBindingIndex = -1; Owner.LevelValueChanged(this); } } }