File: Interactivity\TriggerBase.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
 
namespace Microsoft.Maui.Controls
{
	/// <include file="../../../docs/Microsoft.Maui.Controls/TriggerBase.xml" path="Type[@FullName='Microsoft.Maui.Controls.TriggerBase']/Docs/*" />
	public abstract class TriggerBase : BindableObject, IAttachedObject
	{
		bool _isSealed;
 
		internal TriggerBase(Type targetType)
		{
			TargetType = targetType ?? throw new ArgumentNullException(nameof(targetType));
 
			EnterActions = new SealedList<TriggerAction>();
			ExitActions = new SealedList<TriggerAction>();
		}
 
		internal TriggerBase(Condition condition, Type targetType) : this(targetType)
		{
			Setters = new SealedList<Setter>();
			Condition = condition;
			Condition.ConditionChanged = OnConditionChanged;
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/TriggerBase.xml" path="//Member[@MemberName='EnterActions']/Docs/*" />
		public IList<TriggerAction> EnterActions { get; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/TriggerBase.xml" path="//Member[@MemberName='ExitActions']/Docs/*" />
		public IList<TriggerAction> ExitActions { get; }
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/TriggerBase.xml" path="//Member[@MemberName='IsSealed']/Docs/*" />
		public bool IsSealed
		{
			get { return _isSealed; }
			private set
			{
				if (_isSealed == value)
					return;
				if (!value)
					throw new InvalidOperationException("What is sealed cannot be unsealed.");
				_isSealed = value;
				OnSeal();
			}
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/TriggerBase.xml" path="//Member[@MemberName='TargetType']/Docs/*" />
		public Type TargetType { get; }
 
		internal Condition Condition { get; }
 
		//Setters and Condition are used by Trigger, DataTrigger and MultiTrigger
		internal IList<Setter> Setters { get; }
 
		void IAttachedObject.AttachTo(BindableObject bindable)
		{
			IsSealed = true;
 
			if (bindable == null)
				throw new ArgumentNullException(nameof(bindable));
			if (!TargetType.IsInstanceOfType(bindable))
				throw new InvalidOperationException("bindable not an instance of AssociatedType");
			OnAttachedTo(bindable);
		}
 
		void IAttachedObject.DetachFrom(BindableObject bindable)
		{
			if (bindable == null)
				throw new ArgumentNullException(nameof(bindable));
			OnDetachingFrom(bindable);
		}
 
		internal virtual void OnAttachedTo(BindableObject bindable)
		{
			if (Condition != null)
			{
				var triggerIndex = ++bindable._triggerCount;
				var manualSpecificity = (ushort)(SetterSpecificity.ManualTriggerBaseline + triggerIndex);
				var specificity = new SetterSpecificity(0, manualSpecificity, 0, 0, 0, 0, 0, 0);
 
				bindable._triggerSpecificity[this] = specificity;
				Condition.SetUp(bindable);
			}
		}
 
		internal virtual void OnDetachingFrom(BindableObject bindable)
		{
			if (Condition != null)
			{
				Condition.TearDown(bindable);
				bindable._triggerSpecificity.Remove(this);
			}
		}
 
		internal virtual void OnSeal()
		{
			((SealedList<TriggerAction>)EnterActions).IsReadOnly = true;
			((SealedList<TriggerAction>)ExitActions).IsReadOnly = true;
			if (Setters != null)
				((SealedList<Setter>)Setters).IsReadOnly = true;
			if (Condition != null)
				Condition.IsSealed = true;
		}
 
		void OnConditionChanged(BindableObject bindable, bool oldValue, bool newValue)
		{
			if (!bindable._triggerSpecificity.TryGetValue(this, out var specificity))
			{
				// this should never happen
				return;
			}
 
			if (newValue)
			{
				foreach (TriggerAction action in EnterActions)
					action.DoInvoke(bindable);
				foreach (Setter setter in Setters)
					setter.Apply(bindable, specificity);
			}
			else
			{
				foreach (Setter setter in Setters)
					setter.UnApply(bindable, specificity);
				foreach (TriggerAction action in ExitActions)
					action.DoInvoke(bindable);
			}
		}
 
		internal class SealedList<T> : IList<T>
		{
			readonly IList<T> _actual;
 
			bool _isReadOnly;
 
			public SealedList()
			{
				_actual = new List<T>();
			}
 
			public void Add(T item)
			{
				if (IsReadOnly)
					throw new InvalidOperationException("This list is ReadOnly");
				_actual.Add(item);
			}
 
			public void Clear()
			{
				if (IsReadOnly)
					throw new InvalidOperationException("This list is ReadOnly");
				_actual.Clear();
			}
 
			public bool Contains(T item)
			{
				return _actual.Contains(item);
			}
 
			public void CopyTo(T[] array, int arrayIndex)
			{
				_actual.CopyTo(array, arrayIndex);
			}
 
			public int Count
			{
				get { return _actual.Count; }
			}
 
			public bool IsReadOnly
			{
				get { return _isReadOnly; }
				set
				{
					if (_isReadOnly == value)
						return;
					if (!value)
						throw new InvalidOperationException("Can't change this back to non readonly");
					_isReadOnly = value;
				}
			}
 
			public bool Remove(T item)
			{
				if (IsReadOnly)
					throw new InvalidOperationException("This list is ReadOnly");
				return _actual.Remove(item);
			}
 
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable)_actual).GetEnumerator();
			}
 
			public IEnumerator<T> GetEnumerator()
			{
				return _actual.GetEnumerator();
			}
 
			public int IndexOf(T item)
			{
				return _actual.IndexOf(item);
			}
 
			public void Insert(int index, T item)
			{
				if (IsReadOnly)
					throw new InvalidOperationException("This list is ReadOnly");
				_actual.Insert(index, item);
			}
 
			public T this[int index]
			{
				get { return _actual[index]; }
				set
				{
					if (IsReadOnly)
						throw new InvalidOperationException("This list is ReadOnly");
					_actual[index] = value;
				}
			}
 
			public void RemoveAt(int index)
			{
				if (IsReadOnly)
					throw new InvalidOperationException("This list is ReadOnly");
				_actual.RemoveAt(index);
			}
		}
	}
}