mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-11 07:48:36 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
392 lines
8.1 KiB
C#
392 lines
8.1 KiB
C#
//
|
|
//
|
|
// Selectors = [selector], [selector], [selector]
|
|
// Selector = [rule] [rule] [rule] [rule]
|
|
// rule = ELEMENT.Class.Class.Class:hover:and:stuff
|
|
//
|
|
|
|
namespace Sandbox.UI;
|
|
|
|
internal static partial class StyleParser
|
|
{
|
|
/// <summary>
|
|
/// Here we divide the selectors into groups
|
|
/// .fucker, .cocks, .hairy
|
|
/// </summary>
|
|
public static List<StyleSelector> Selector( string rule_string, StyleBlock parent = null )
|
|
{
|
|
var list = new List<StyleSelector>();
|
|
|
|
var p = new Parse( rule_string );
|
|
|
|
while ( !p.IsEnd )
|
|
{
|
|
p = p.SkipWhitespaceAndNewlines();
|
|
|
|
if ( p.IsEnd )
|
|
break;
|
|
|
|
if ( p.Current == ',' )
|
|
throw new System.Exception( $"Invalid Selector: {rule_string}" );
|
|
|
|
var group = p.ReadUntilOrEnd( "," );
|
|
|
|
group = group.Trim();
|
|
|
|
var ss = ParseSelector( group, parent );
|
|
if ( ss == null )
|
|
return null;
|
|
|
|
list.Add( ss );
|
|
|
|
if ( !p.IsEnd && p.Current == ',' )
|
|
p.Pointer++;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
public static StyleSelector ParseSelector( string rule_string, StyleBlock parent = null )
|
|
{
|
|
var p = new Parse( rule_string );
|
|
|
|
StyleSelector lastRule = null;
|
|
|
|
while ( !p.IsEnd )
|
|
{
|
|
p = p.SkipWhitespaceAndNewlines();
|
|
|
|
if ( p.IsEnd )
|
|
break;
|
|
|
|
bool immediateParent = false;
|
|
|
|
if ( p.Is( '>' ) )
|
|
{
|
|
p.Pointer++;
|
|
p = p.SkipWhitespaceAndNewlines();
|
|
immediateParent = true;
|
|
}
|
|
|
|
var selector = p.ReadUntilWhitespaceOrNewlineOrEndAndObeyBrackets();
|
|
|
|
var rule = ParseSingleSelector( selector, parent );
|
|
|
|
if ( rule == null )
|
|
return null;
|
|
|
|
rule.Parent = lastRule;
|
|
rule.ImmediateParent = immediateParent;
|
|
lastRule = rule;
|
|
}
|
|
|
|
if ( lastRule != null )
|
|
{
|
|
lastRule.AsString = rule_string.Trim();
|
|
|
|
if ( parent != null )
|
|
{
|
|
var parentRules = string.Join( ", ", parent.Selectors.Select( x => x.AsString ) );
|
|
if ( lastRule.AsString.StartsWith( '&' ) )
|
|
{
|
|
lastRule.AsString = parentRules + lastRule.AsString.Substring( 1 );
|
|
}
|
|
else
|
|
{
|
|
lastRule.AsString = parentRules + " " + lastRule.AsString;
|
|
}
|
|
}
|
|
}
|
|
|
|
return lastRule;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse a single rule, which as "panel.closed.error:hover"
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static StyleSelector ParseSingleSelector( string rule_string, StyleBlock parent )
|
|
{
|
|
var seperators = ".:";
|
|
var rule = new StyleSelector();
|
|
rule.AsString = rule_string.Trim();
|
|
|
|
var p = new Parse( rule_string );
|
|
|
|
p = p.SkipWhitespaceAndNewlines();
|
|
|
|
List<string> ruleClasses = null;
|
|
|
|
//
|
|
// If our selector starts with & we need to match any of the parent block's selectors
|
|
//
|
|
if ( p.Current == '&' )
|
|
{
|
|
p.Pointer++;
|
|
|
|
if ( parent == null )
|
|
throw new System.Exception( $"Starts with & but has no parent block \"{rule_string}\"" );
|
|
|
|
rule.AnyOf = parent.Selectors;
|
|
}
|
|
else if ( p.Current == '>' )
|
|
{
|
|
p.Pointer++;
|
|
|
|
if ( parent == null )
|
|
throw new System.Exception( $"Starts with > but has no parent block \"{rule_string}\"" );
|
|
|
|
rule.DecendantOf = parent.Selectors;
|
|
rule.ImmediateParent = true;
|
|
}
|
|
else if ( parent != null )
|
|
{
|
|
//
|
|
// If we have a parent block, our parent needs to conform to its rules
|
|
//
|
|
rule.DecendantOf = parent.Selectors;
|
|
}
|
|
|
|
while ( !p.IsEnd )
|
|
{
|
|
//
|
|
// Class
|
|
//
|
|
if ( p.Current == '.' )
|
|
{
|
|
p.Pointer++;
|
|
|
|
if ( p.IsEnd || p.IsOneOf( seperators ) )
|
|
throw new System.Exception( $"Invalid Rule \"{rule_string}\"" );
|
|
|
|
var classname = p.ReadUntilOrEnd( ".:#" ).ToLowerInvariant();
|
|
|
|
ruleClasses ??= new();
|
|
ruleClasses.Add( classname );
|
|
}
|
|
else if ( p.Current == '#' )
|
|
{
|
|
p.Pointer++;
|
|
|
|
if ( p.IsEnd || p.IsOneOf( seperators ) )
|
|
throw new System.Exception( $"Invalid Rule \"{rule_string}\"" );
|
|
|
|
var id = p.ReadUntilOrEnd( ".:#" ).ToLower();
|
|
rule.Id = id;
|
|
}
|
|
else if ( p.Current == ':' )
|
|
{
|
|
// there might be 2, skip them all
|
|
while ( p.Current == ':' )
|
|
p.Pointer++;
|
|
|
|
if ( p.IsEnd || p.IsOneOf( seperators ) )
|
|
throw new System.Exception( $"Invalid Rule \"{rule_string}\"" );
|
|
|
|
ReadPseudoClass( rule, ref p );
|
|
}
|
|
else if ( p.Current == '*' )
|
|
{
|
|
p.Pointer++;
|
|
rule.UniversalSelector = true;
|
|
|
|
if ( !p.IsEnd && !p.IsOneOf( seperators ) )
|
|
throw new System.Exception( $"Invalid Rule \"{rule_string}\"" );
|
|
}
|
|
else
|
|
{
|
|
rule.Element = p.ReadUntilOrEnd( ".:#" ).ToLower();
|
|
}
|
|
}
|
|
|
|
if ( ruleClasses != null )
|
|
{
|
|
rule.SetClasses( ruleClasses.ToArray() );
|
|
}
|
|
|
|
return rule;
|
|
}
|
|
|
|
private static void ReadPseudoClass( StyleSelector rule, ref Parse p )
|
|
{
|
|
if ( p.Is( "has(", 0, true ) )
|
|
{
|
|
p.Pointer += 3;
|
|
var inner = p.ReadInnerBrackets();
|
|
rule.Has = ParseHasSelectors( inner );
|
|
return;
|
|
}
|
|
|
|
if ( p.Is( "not(", 0, true ) )
|
|
{
|
|
p.Pointer += 3;
|
|
var inner = p.ReadInnerBrackets();
|
|
if ( string.IsNullOrEmpty( inner ) ) return;
|
|
|
|
rule.Not = ParseSelector( inner );
|
|
return;
|
|
}
|
|
|
|
if ( p.Is( "nth-child(", 0, true ) )
|
|
{
|
|
p.Pointer += "nth-child".Length;
|
|
var inner = p.ReadInnerBrackets();
|
|
if ( string.IsNullOrEmpty( inner ) ) return;
|
|
|
|
ParseNthChild( rule, inner.Trim() );
|
|
|
|
return;
|
|
}
|
|
|
|
var flagname = p.ReadUntilOrEnd( ".:" ).ToLower();
|
|
|
|
switch ( flagname )
|
|
{
|
|
case "hover":
|
|
rule.Flags |= PseudoClass.Hover;
|
|
break;
|
|
|
|
case "active":
|
|
rule.Flags |= PseudoClass.Active;
|
|
break;
|
|
|
|
case "focus":
|
|
rule.Flags |= PseudoClass.Focus;
|
|
break;
|
|
|
|
case "intro":
|
|
rule.Flags |= PseudoClass.Intro;
|
|
break;
|
|
|
|
case "outro":
|
|
rule.Flags |= PseudoClass.Outro;
|
|
break;
|
|
|
|
case "empty":
|
|
rule.Flags |= PseudoClass.Empty;
|
|
break;
|
|
|
|
case "first-child":
|
|
rule.Flags |= PseudoClass.FirstChild;
|
|
break;
|
|
|
|
case "last-child":
|
|
rule.Flags |= PseudoClass.LastChild;
|
|
break;
|
|
|
|
case "only-child":
|
|
rule.Flags |= PseudoClass.OnlyChild;
|
|
break;
|
|
|
|
case "before":
|
|
rule.Flags |= PseudoClass.Before;
|
|
break;
|
|
|
|
case "after":
|
|
rule.Flags |= PseudoClass.After;
|
|
break;
|
|
|
|
default:
|
|
throw new System.Exception( $"Unsupported Pseudo Class \"{flagname}\"" );
|
|
}
|
|
}
|
|
|
|
private static void ParseNthChild( StyleSelector rule, string inner )
|
|
{
|
|
if ( int.TryParse( inner, out int intValue ) )
|
|
{
|
|
rule.NthChild = ( p ) => (p.SiblingIndex + 1) == intValue;
|
|
return;
|
|
}
|
|
|
|
if ( string.Equals( inner, "odd", StringComparison.OrdinalIgnoreCase ) )
|
|
{
|
|
rule.NthChild = ( p ) => p.SiblingIndex % 2 == 0;
|
|
return;
|
|
}
|
|
|
|
if ( string.Equals( inner, "even", StringComparison.OrdinalIgnoreCase ) )
|
|
{
|
|
rule.NthChild = ( p ) => p.SiblingIndex % 2 == 1;
|
|
return;
|
|
}
|
|
|
|
throw new System.Exception( $"unsupported NthChild \"{inner}\"" );
|
|
}
|
|
|
|
private static StyleSelector[] ParseHasSelectors( string inner )
|
|
{
|
|
if ( string.IsNullOrWhiteSpace( inner ) )
|
|
return null;
|
|
|
|
var selectors = new List<StyleSelector>();
|
|
|
|
foreach ( var part in inner.Split( ',' ) )
|
|
{
|
|
var trimmed = part.Trim();
|
|
if ( string.IsNullOrEmpty( trimmed ) ) continue;
|
|
|
|
var selector = ParseHasSelector( trimmed );
|
|
if ( selector == null ) continue;
|
|
|
|
selectors.Add( selector );
|
|
}
|
|
|
|
if ( selectors.Count == 0 )
|
|
return null;
|
|
|
|
return selectors.ToArray();
|
|
}
|
|
|
|
private static StyleSelector ParseHasSelector( string selectorString )
|
|
{
|
|
if ( string.IsNullOrWhiteSpace( selectorString ) )
|
|
return null;
|
|
|
|
var p = new Parse( selectorString );
|
|
p = p.SkipWhitespaceAndNewlines();
|
|
|
|
// Handle different combinator types at the start
|
|
bool isDirectChild = false;
|
|
bool isAdjacent = false;
|
|
bool isGeneral = false;
|
|
|
|
if ( !p.IsEnd && p.Current == '>' )
|
|
{
|
|
isDirectChild = true;
|
|
p.Pointer++;
|
|
p = p.SkipWhitespaceAndNewlines();
|
|
}
|
|
else if ( !p.IsEnd && p.Current == '+' )
|
|
{
|
|
isAdjacent = true;
|
|
p.Pointer++;
|
|
p = p.SkipWhitespaceAndNewlines();
|
|
}
|
|
else if ( !p.IsEnd && p.Current == '~' )
|
|
{
|
|
isGeneral = true;
|
|
p.Pointer++;
|
|
p = p.SkipWhitespaceAndNewlines();
|
|
}
|
|
|
|
if ( p.IsEnd ) return null;
|
|
|
|
var remaining = p.ReadRemaining();
|
|
if ( string.IsNullOrWhiteSpace( remaining ) )
|
|
return null;
|
|
|
|
var selector = ParseSingleSelector( remaining, null );
|
|
|
|
if ( selector != null )
|
|
{
|
|
if ( isDirectChild ) selector.ImmediateParent = true;
|
|
else if ( isAdjacent ) selector.AdjacentSibling = true;
|
|
else if ( isGeneral ) selector.GeneralSibling = true;
|
|
}
|
|
|
|
return selector;
|
|
}
|
|
|
|
}
|