// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; namespace osu.Framework.Bindables { /// /// Combines multiple bindables into one aggregate bindable result. /// /// The type of values. public class AggregateBindable { private readonly Func aggregateFunction; /// /// The final result after aggregating all added sources. /// public IBindable Result => result; private readonly Bindable result; private readonly T initialValue; /// /// Create a new aggregate bindable. /// /// The function to be used for aggregation, taking two input values and returning one output. /// An optional newly constructed bindable to use for . The initial value of this bindable is used as the initial value for the aggregate. public AggregateBindable(Func aggregateFunction, Bindable resultBindable = null) { this.aggregateFunction = aggregateFunction; result = resultBindable ?? new Bindable(); initialValue = result.Value; } private readonly List sourceMapping = new List(); /// /// Add a new source to be included in aggregation. /// /// The bindable to add. public void AddSource(IBindable bindable) { lock (sourceMapping) { if (findExistingPair(bindable) != null) return; var boundCopy = bindable.GetBoundCopy(); sourceMapping.Add(new WeakRefPair(new WeakReference>(bindable), boundCopy)); boundCopy.BindValueChanged(recalculateAggregate, true); } } /// /// Remove a source from being included in aggregation. /// /// The bindable to remove. public void RemoveSource(IBindable bindable) { lock (sourceMapping) { var weak = findExistingPair(bindable); if (weak != null) { weak.BoundCopy.UnbindAll(); sourceMapping.Remove(weak); } recalculateAggregate(); } } private WeakRefPair findExistingPair(IBindable bindable) => sourceMapping.FirstOrDefault(p => p.WeakReference.TryGetTarget(out var target) && target == bindable); private void recalculateAggregate(ValueChangedEvent obj = null) { T calculated = initialValue; lock (sourceMapping) { for (var i = 0; i < sourceMapping.Count; i++) { var pair = sourceMapping[i]; if (!pair.WeakReference.TryGetTarget(out _)) sourceMapping.RemoveAt(i--); else calculated = aggregateFunction(calculated, pair.BoundCopy.Value); } } result.Value = calculated; } public void RemoveAllSources() { lock (sourceMapping) { foreach (var mapping in sourceMapping.ToArray()) { if (mapping.WeakReference.TryGetTarget(out var b)) RemoveSource(b); } } } private class WeakRefPair { public readonly WeakReference> WeakReference; public readonly IBindable BoundCopy; public WeakRefPair(WeakReference> weakReference, IBindable boundCopy) { WeakReference = weakReference; BoundCopy = boundCopy; } } } }