File: SetterSpecificity.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Runtime.CompilerServices;
 
namespace Microsoft.Maui.Controls
{
	/// <summary>
	/// Defines a setter specificity
	/// </summary>
	/// <remarks>
	/// We still can refine the specificities, but here is how they're compared right now:
	/// - DefaultValue has the lowest priority
	/// - Everything coming from a Style is low priority
	/// - Binding, DynamicResource, Manual (in that order)
	/// - Values set from VSM have a higher priority
	/// 
	/// Then everything coming from the Handlers has a special priority. it is always applied, but is overridden by almost everything else
	/// </remarks>
	internal readonly struct SetterSpecificity
	{
		const byte ExtrasVsm = 0x01;
		const byte ExtrasHandler = 0xFF;
 
		public const ushort ManualTriggerBaseline = 2;
 
		public const ushort StyleImplicit = 0x080;
		public const ushort StyleLocal = 0x100;
 
		public static readonly SetterSpecificity DefaultValue = new SetterSpecificity(0);
		public static readonly SetterSpecificity VisualStateSetter = new SetterSpecificity(ExtrasVsm, 0, 0, 0, 0, 0, 0, 0);
		public static readonly SetterSpecificity FromBinding = new SetterSpecificity(0, 0, 0, 1, 0, 0, 0, 0);
 
		public static readonly SetterSpecificity ManualValueSetter = new SetterSpecificity(0, 1, 0, 0, 0, 0, 0, 0);
		public static readonly SetterSpecificity Trigger = new SetterSpecificity(0, ManualTriggerBaseline, 0, 0, 0, 0, 0, 0);
 
		public static readonly SetterSpecificity DynamicResourceSetter = new SetterSpecificity(0, 0, 1, 0, 0, 0, 0, 0);
 
		// handler always apply, but are removed when anything else comes in. see SetValueActual
		public static readonly SetterSpecificity FromHandler = new SetterSpecificity(0xFF, 0, 0, 0, 0, 0, 0, 0);
 
		// We store all information in one single UInt64 value to have the fastest comparison possible
		readonly ulong _value;
 
 
		public bool IsDefault => _value == 0ul;
		public bool IsHandler => _value == 0xFFFFFFFFFFFFFFFF;
		public bool IsVsm => (_value & 0x0100000000000000) != 0;
		public bool IsVsmImplicit => (_value & 0x0000000004000000) != 0;
		public bool IsManual => ((_value >> 28) & 0xFFFF) == 1;
		public ushort TriggerIndex => GetTriggerIndex();
		public bool IsDynamicResource => ((_value >> 24) & 0x02) != 0;
		public bool IsBinding => ((_value >> 24) & 0x01) != 0;
		public (ushort Style, byte Id, byte Class, byte Type) StyleInfo => GetStyleInfo();
 
		ushort GetTriggerIndex()
		{
			var manual = (ushort)((_value >> 28) & 0xFFFF);
			if (manual <= 1)
				return 0;
			return (ushort)(manual - 2);
		}
 
		(ushort Style, byte Id, byte Class, byte Type) GetStyleInfo()
		{
			var style = (ushort)((_value >> 44) & 0xFFF);
			if (style == 0xFFF)
				return default;
			return (style, (byte)((_value >> 16) & 0xFF), (byte)((_value >> 8) & 0xFF), (byte)(_value & 0xFF));
		}
 
 
		/// <summary>
		/// Creates a new setter specificity
		/// </summary>
		/// <param name="extras">
		/// Specifies special setter sources <br />
		/// - 1: from VSM <br />
		/// - 0xFF: from Handler
		/// </param>
		/// <param name="manual">
		/// Determines manual specificity, also covers triggers <br />
		/// - 0: not manual <br />
		/// - 1..100: manual <br />
		/// - 101..N: triggers <br />
		/// </param>
		/// <param name="isDynamicResource">Set to 1 when value comes from dynamic resource, otherwise 0</param>
		/// <param name="isBinding">Set to 1 when value comes from binding, otherwise 0</param>
		/// <param name="style">
		/// XAML Style specificity <br />
		/// - 0: not from Style <br />
		/// - 127: base implicit style
		/// - 128-n: implicit style, n = max(99, distance between the RD and the target) <br />
		/// - 255: base local style
		/// - 256-n: local style, inline css, <br />
		/// </param>
		/// <param name="id">
		/// CSS Id Specificity <br />
		/// See https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity
		/// </param>
		/// <param name="class">
		/// CSS Class Specificity <br />
		/// See https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity
		/// </param>
		/// <param name="type">
		/// CSS Type Specificity <br />
		/// See https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity
		/// </param>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		public SetterSpecificity(byte extras, ushort manual, byte isDynamicResource, byte isBinding, ushort style, byte id, byte @class, byte type)
		{
			// Handlers are special, they win on everything else
			if (extras == ExtrasHandler)
			{
				_value = 0xFFFFFFFFFFFFFFFF;
				return;
			}
 
			// If no style is set, set it to a value which supersedes any other style value
			if (style == 0)
			{
				style = 0xFFF;
				id = @class = type = 0xFF;
			}
 
			// Priority order:
			//                       64bit ulong value
			// 1. VSM                0x0100000000000000
			// 2. Style              0x00FFF00000000000
			// 3. Manual(& Trigger)  0x00000FFFF0000000
			// 4. Implicit VSM       0x0000000004000000
			// 4. DynamicResource    0x0000000002000000
			// 5. Binding            0x0000000001000000
			// 6. Id                 0x0000000000FF0000
			// 7. Class              0x000000000000FF00
			// 8. Type               0x00000000000000FF
 
			var implicitVsm = 0;
			var vsm = extras == ExtrasVsm ? 0x01 : 0;
			var binding = isBinding > 0 ? 0x01 : 0;
			var dynamicResource = isDynamicResource > 0 ? 0x02 : 0;
 
			// Implicit style VSM has less priority than manually set values
			// See https://github.com/dotnet/maui/issues/18103
			const int styleImplicitUpperBound = StyleLocal - 1;
			if (vsm != 0 && style < styleImplicitUpperBound)
			{
				implicitVsm = 0x04;
				vsm = 0;
			}
 
			_value = type
					 | (ulong)@class << 8
					 | (ulong)id << 16
					 | (ulong)(implicitVsm | dynamicResource | binding) << 24
					 | (ulong)manual << 28
					 | (ulong)style << 44
					 | (ulong)vsm << 56
				;
		}
 
		public SetterSpecificity(ushort style, byte id, byte @class, byte type) : this(0, 0, 0, 0, style, id, @class, type)
		{
		}
 
		public SetterSpecificity()
		{
			// When no parameter have been specified for the specificity, just use the lowest value possible
			// This value is still higher than the DefaultValue, so it will be applied
			_value = 1;
		}
 
		/// <summary>
		/// Special private constructor to create DefaultValue specificity
		/// </summary>
		SetterSpecificity(ulong value)
		{
			_value = value;
		}
 
		public SetterSpecificity CopyStyle(byte extras, ushort manual, byte isDynamicResource, byte isBinding)
		{
			return new SetterSpecificity(
				extras,
				manual,
				isDynamicResource,
				isBinding,
				style: (ushort)((_value >> 44) & 0xFFF),
				id: (byte)((_value >> 16) & 0xFF),
				@class: (byte)((_value >> 8) & 0xFF),
				type: (byte)(_value & 0xFF));
		}
 
		public SetterSpecificity AsBaseStyle()
		{
			return new SetterSpecificity(_value - 0x0000100000000000);
		}
 
		public override bool Equals(object obj) => obj is SetterSpecificity s && s._value == _value;
		public override int GetHashCode() => _value.GetHashCode();
 
		public static bool operator <(SetterSpecificity left, SetterSpecificity right) => left._value < right._value;
		public static bool operator >(SetterSpecificity left, SetterSpecificity right) => left._value > right._value;
		public static bool operator >=(SetterSpecificity left, SetterSpecificity right) => left._value >= right._value;
		public static bool operator <=(SetterSpecificity left, SetterSpecificity right) => left._value <= right._value;
		public static bool operator ==(SetterSpecificity left, SetterSpecificity right) => left._value == right._value;
		public static bool operator !=(SetterSpecificity left, SetterSpecificity right) => left._value != right._value;
	}
}