using System; using Avalonia.Styling.Activators; #nullable enable namespace Avalonia.Styling { /// /// A selector in a . /// public abstract class Selector { /// /// Gets a value indicating whether either this selector or a previous selector has moved /// into a template. /// internal abstract bool InTemplate { get; } /// /// Gets a value indicating whether this selector is a combinator. /// /// /// A combinator is a selector such as Child or Descendent which links simple selectors. /// internal abstract bool IsCombinator { get; } /// /// Gets the target type of the selector, if available. /// internal abstract Type? TargetType { get; } /// /// Tries to match the selector with a control. /// /// The control. /// /// The parent style, if the style containing the selector is a nested style. /// /// /// Whether the match should subscribe to changes in order to track the match over time, /// or simply return an immediate result. /// /// A . internal SelectorMatch Match(StyledElement control, IStyle? parent = null, bool subscribe = true) { // First match the selector until a combinator is found. Selectors are stored from // right-to-left, so MatchUntilCombinator reverses this order because the type selector // will be on the left. var match = MatchUntilCombinator(control, this, parent, subscribe, out var combinator); // If the pre-combinator selector matches, we can now match the combinator, if any. if (match.IsMatch && combinator is object) { match = match.And(combinator.Match(control, parent, subscribe)); // If we have a combinator then we can never say that we always match a control of // this type, because by definition the combinator matches on things outside of the // control. match = match.Result switch { SelectorMatchResult.AlwaysThisType => SelectorMatch.AlwaysThisInstance, SelectorMatchResult.NeverThisType => SelectorMatch.NeverThisInstance, _ => match }; } return match; } public override string ToString() => ToString(null); /// /// Gets a string representing the selector, with the nesting separator (`^`) replaced with /// the parent selector. /// /// The owner style. public abstract string ToString(Style? owner); /// /// Whether there is a selector that comes after this one. internal virtual string ToString(Style? owner, bool hasNext) => ToString(owner); /// /// Evaluates the selector for a match. /// /// The control. /// /// The parent style, if the style containing the selector is a nested style. /// /// /// Whether the match should subscribe to changes in order to track the match over time, /// or simply return an immediate result. /// /// A . private protected abstract SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe); /// /// Moves to the previous selector. /// private protected abstract Selector? MovePrevious(); /// /// Moves to the previous selector or the parent selector. /// private protected abstract Selector? MovePreviousOrParent(); internal virtual void ValidateNestingSelector(bool inControlTheme, int templateCount = 0) { var s = this; if (inControlTheme) { if (!s.InTemplate && s.IsCombinator) throw new InvalidOperationException( "ControlTheme style may not directly contain a child or descendent selector."); if (s is TemplateSelector && templateCount++ > 0) throw new InvalidOperationException( "ControlTemplate styles cannot contain multiple template selectors."); } var previous = s.MovePreviousOrParent(); if (previous is null) { if (s is not NestingSelector) throw new InvalidOperationException("Child styles must have a nesting selector."); } else { previous.ValidateNestingSelector(inControlTheme, templateCount); } } private static SelectorMatch MatchUntilCombinator( StyledElement control, Selector start, IStyle? parent, bool subscribe, out Selector? combinator) { combinator = null; var activators = new AndActivatorBuilder(); var result = Match(control, start, parent, subscribe, ref activators, ref combinator); return result == SelectorMatchResult.Sometimes ? new SelectorMatch(activators.Get()) : new SelectorMatch(result); } private static SelectorMatchResult Match( StyledElement control, Selector selector, IStyle? parent, bool subscribe, ref AndActivatorBuilder activators, ref Selector? combinator) { var previous = selector.MovePrevious(); // Selectors are stored from right-to-left, so we recurse into the selector in order to // reverse this order, because the type selector will be on the left and is our best // opportunity to exit early. if (previous != null && !previous.IsCombinator) { var previousMatch = Match(control, previous, parent, subscribe, ref activators, ref combinator); if (previousMatch < SelectorMatchResult.Sometimes) { return previousMatch; } } SelectorMatch match = SelectorMatch.NeverThisInstance; bool containerMatchesSometimes = false; // Match any parent Container query if (parent is ContainerQuery container) { match = container.Query?.Evaluate(control, container.Parent, subscribe, container.Name) ?? SelectorMatch.NeverThisInstance; if (!match.IsMatch) return match.Result; containerMatchesSometimes = match.Result == SelectorMatchResult.Sometimes; if (containerMatchesSometimes) activators.Add(match.Activator!); } // Match this selector. match = selector.Evaluate(control, parent, subscribe); if (!match.IsMatch) { combinator = null; return match.Result; } else if (match.Activator is object) { activators.Add(match.Activator!); } if (previous?.IsCombinator == true) { combinator = previous; } return containerMatchesSometimes ? SelectorMatchResult.Sometimes : match.Result; } } }