PriorityValue.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. // Copyright (c) The Perspex Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Reactive.Subjects;
  7. using System.Text;
  8. using Perspex.Utilities;
  9. namespace Perspex
  10. {
  11. /// <summary>
  12. /// Maintains a list of prioritised bindings together with a current value.
  13. /// </summary>
  14. /// <remarks>
  15. /// Bindings, in the form of <see cref="IObservable{Object}"/>s are added to the object using
  16. /// the <see cref="Add"/> method. With the observable is passed a priority, where lower values
  17. /// represent higher priorites. The current <see cref="Value"/> is selected from the highest
  18. /// priority binding that doesn't return <see cref="PerspexProperty.UnsetValue"/>. Where there
  19. /// are multiple bindings registered with the same priority, the most recently added binding
  20. /// has a higher priority. Each time the value changes, the <see cref="Changed"/> observable is
  21. /// fired with the old and new values.
  22. /// </remarks>
  23. internal class PriorityValue
  24. {
  25. /// <summary>
  26. /// The name of the property.
  27. /// </summary>
  28. private readonly string _name;
  29. /// <summary>
  30. /// The value type.
  31. /// </summary>
  32. private readonly Type _valueType;
  33. /// <summary>
  34. /// The currently registered bindings organised by priority.
  35. /// </summary>
  36. private readonly Dictionary<int, PriorityLevel> _levels = new Dictionary<int, PriorityLevel>();
  37. /// <summary>
  38. /// The changed observable.
  39. /// </summary>
  40. private readonly Subject<Tuple<object, object>> _changed = new Subject<Tuple<object, object>>();
  41. /// <summary>
  42. /// The current value.
  43. /// </summary>
  44. private object _value;
  45. /// <summary>
  46. /// The function used to validate the value, if any.
  47. /// </summary>
  48. private readonly Func<object, object> _validate;
  49. /// <summary>
  50. /// Initializes a new instance of the <see cref="PriorityValue"/> class.
  51. /// </summary>
  52. /// <param name="name">The name of the property.</param>
  53. /// <param name="valueType">The value type.</param>
  54. /// <param name="validate">An optional validation function.</param>
  55. public PriorityValue(string name, Type valueType, Func<object, object> validate = null)
  56. {
  57. _name = name;
  58. _valueType = valueType;
  59. _value = PerspexProperty.UnsetValue;
  60. ValuePriority = int.MaxValue;
  61. _validate = validate;
  62. }
  63. /// <summary>
  64. /// Fired whenever the current <see cref="Value"/> changes.
  65. /// </summary>
  66. /// <remarks>
  67. /// The old and new values may be the same, this class does not check for distinct values.
  68. /// </remarks>
  69. public IObservable<Tuple<object, object>> Changed => _changed;
  70. /// <summary>
  71. /// Gets the current value.
  72. /// </summary>
  73. public object Value => _value;
  74. /// <summary>
  75. /// Gets the priority of the binding that is currently active.
  76. /// </summary>
  77. public int ValuePriority
  78. {
  79. get;
  80. private set;
  81. }
  82. /// <summary>
  83. /// Adds a new binding.
  84. /// </summary>
  85. /// <param name="binding">The binding.</param>
  86. /// <param name="priority">The binding priority.</param>
  87. /// <returns>
  88. /// A disposable that will remove the binding.
  89. /// </returns>
  90. public IDisposable Add(IObservable<object> binding, int priority)
  91. {
  92. return GetLevel(priority).Add(binding);
  93. }
  94. /// <summary>
  95. /// Sets the value for a specified priority.
  96. /// </summary>
  97. /// <param name="value">The value.</param>
  98. /// <param name="priority">The priority</param>
  99. public void SetValue(object value, int priority)
  100. {
  101. GetLevel(priority).DirectValue = value;
  102. }
  103. /// <summary>
  104. /// Gets the currently active bindings on this object.
  105. /// </summary>
  106. /// <returns>An enumerable collection of bindings.</returns>
  107. public IEnumerable<PriorityBindingEntry> GetBindings()
  108. {
  109. foreach (var level in _levels)
  110. {
  111. foreach (var binding in level.Value.Bindings)
  112. {
  113. yield return binding;
  114. }
  115. }
  116. }
  117. /// <summary>
  118. /// Returns diagnostic string that can help the user debug the bindings in effect on
  119. /// this object.
  120. /// </summary>
  121. /// <returns>A diagnostic string.</returns>
  122. public string GetDiagnostic()
  123. {
  124. var b = new StringBuilder();
  125. var first = true;
  126. foreach (var level in _levels)
  127. {
  128. if (!first)
  129. {
  130. b.AppendLine();
  131. }
  132. b.Append(ValuePriority == level.Key ? "*" : string.Empty);
  133. b.Append("Priority ");
  134. b.Append(level.Key);
  135. b.Append(": ");
  136. b.AppendLine(level.Value.Value?.ToString() ?? "(null)");
  137. b.AppendLine("--------");
  138. b.Append("Direct: ");
  139. b.AppendLine(level.Value.DirectValue?.ToString() ?? "(null)");
  140. foreach (var binding in level.Value.Bindings)
  141. {
  142. b.Append(level.Value.ActiveBindingIndex == binding.Index ? "*" : string.Empty);
  143. b.Append(binding.Description ?? binding.Observable.GetType().Name);
  144. b.Append(": ");
  145. b.AppendLine(binding.Value?.ToString() ?? "(null)");
  146. }
  147. first = false;
  148. }
  149. return b.ToString();
  150. }
  151. /// <summary>
  152. /// Causes a revalidation of the value.
  153. /// </summary>
  154. public void Revalidate()
  155. {
  156. if (_validate != null)
  157. {
  158. PriorityLevel level;
  159. if (_levels.TryGetValue(ValuePriority, out level))
  160. {
  161. UpdateValue(level.Value, level.Priority);
  162. }
  163. }
  164. }
  165. /// <summary>
  166. /// Gets the <see cref="PriorityLevel"/> with the specified priority, creating it if it
  167. /// doesn't already exist.
  168. /// </summary>
  169. /// <param name="priority">The priority.</param>
  170. /// <returns>The priority level.</returns>
  171. private PriorityLevel GetLevel(int priority)
  172. {
  173. PriorityLevel result;
  174. if (!_levels.TryGetValue(priority, out result))
  175. {
  176. var mode = (LevelPrecedenceMode)(priority % 2);
  177. result = new PriorityLevel(priority, mode, ValueChanged);
  178. _levels.Add(priority, result);
  179. }
  180. return result;
  181. }
  182. /// <summary>
  183. /// Updates the current <see cref="Value"/> and notifies all subscibers.
  184. /// </summary>
  185. /// <param name="value">The value to set.</param>
  186. /// <param name="priority">The priority level that the value came from.</param>
  187. private void UpdateValue(object value, int priority)
  188. {
  189. if (TypeUtilities.TryCast(_valueType, value, out value))
  190. {
  191. var old = _value;
  192. if (_validate != null && value != PerspexProperty.UnsetValue)
  193. {
  194. value = _validate(value);
  195. }
  196. ValuePriority = priority;
  197. _value = value;
  198. _changed.OnNext(Tuple.Create(old, _value));
  199. }
  200. else
  201. {
  202. // TODO: Log error.
  203. }
  204. }
  205. /// <summary>
  206. /// Called when the value for a priority level changes.
  207. /// </summary>
  208. /// <param name="level">The priority level of the changed entry.</param>
  209. private void ValueChanged(PriorityLevel level)
  210. {
  211. if (level.Priority <= ValuePriority)
  212. {
  213. if (level.Value != PerspexProperty.UnsetValue)
  214. {
  215. UpdateValue(level.Value, level.Priority);
  216. }
  217. else
  218. {
  219. foreach (var i in _levels.Values.OrderBy(x => x.Priority))
  220. {
  221. if (i.Value != PerspexProperty.UnsetValue)
  222. {
  223. UpdateValue(i.Value, i.Priority);
  224. return;
  225. }
  226. }
  227. UpdateValue(PerspexProperty.UnsetValue, int.MaxValue);
  228. }
  229. }
  230. }
  231. }
  232. }