// ----------------------------------------------------------------------- // // Copyright 2015 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex { using System; using System.Collections.Generic; using System.Reactive.Disposables; /// /// Determines how the current binding is selected for a . /// internal enum LevelPrecedenceMode { /// /// The latest fired binding is used as the current value. /// Latest, /// /// The latest added binding is used as the current value. /// Newest, } /// /// 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 { /// /// Method called when current value changes. /// private Action changed; /// /// The current direct value. /// private object directValue; /// /// The index of the next . /// private int nextIndex; private LevelPrecedenceMode mode; /// /// Initializes a new instance of the class. /// /// The priority. /// A method to be called when the current value changes. public PriorityLevel( int priority, LevelPrecedenceMode mode, Action changed) { Contract.Requires(changed != null); this.mode = mode; this.changed = changed; this.Priority = priority; this.Value = this.directValue = PerspexProperty.UnsetValue; this.ActiveBindingIndex = -1; this.Bindings = new LinkedList(); } /// /// 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 this.directValue; } set { this.Value = this.directValue = value; this.changed(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 = this.Bindings.AddFirst(entry); entry.Start(binding, this.Changed, this.Completed); return Disposable.Create(() => { this.Bindings.Remove(node); if (entry.Index >= this.ActiveBindingIndex) { this.ActivateFirstBinding(); } }); } /// /// Invoked when an entry in changes value. /// /// The entry that changed. private void Changed(PriorityBindingEntry entry) { if (mode == LevelPrecedenceMode.Latest || entry.Index >= this.ActiveBindingIndex) { if (entry.Value != PerspexProperty.UnsetValue) { this.Value = entry.Value; this.ActiveBindingIndex = entry.Index; this.changed(this); } else { this.ActivateFirstBinding(); } } } /// /// Invoked when an entry in completes. /// /// The entry that completed. private void Completed(PriorityBindingEntry entry) { this.Bindings.Remove(entry); if (entry.Index >= this.ActiveBindingIndex) { this.ActivateFirstBinding(); } } /// /// Activates the first binding that has a value. /// private void ActivateFirstBinding() { foreach (var binding in this.Bindings) { if (binding.Value != PerspexProperty.UnsetValue) { this.Value = binding.Value; this.ActiveBindingIndex = binding.Index; this.changed(this); return; } } this.Value = this.DirectValue; this.ActiveBindingIndex = -1; this.changed(this); } } }