mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-16 02:09:20 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
335 lines
6.8 KiB
C#
335 lines
6.8 KiB
C#
namespace Sandbox.UI;
|
|
|
|
/// <summary>
|
|
/// A CSS selector like "Panel.button.red:hover .text"
|
|
/// </summary>
|
|
[SkipHotload]
|
|
public sealed class StyleSelector
|
|
{
|
|
public StyleBlock Block;
|
|
|
|
public string AsString;
|
|
public string[] Classes { get; private set; }
|
|
|
|
public string Element;
|
|
|
|
/// <summary>
|
|
/// The Id selector - minus the #
|
|
/// https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors
|
|
/// </summary>
|
|
public string Id { get; set; }
|
|
public PseudoClass Flags;
|
|
|
|
/// <summary>
|
|
/// Descendant combinator
|
|
/// A B
|
|
/// Child combinator
|
|
/// A > B
|
|
/// Adjacent sibling combinator
|
|
/// A + B
|
|
/// General sibling combinator
|
|
/// A ~B
|
|
/// </summary>
|
|
public StyleSelector Parent;
|
|
public StyleSelector Not;
|
|
public bool ImmediateParent;
|
|
|
|
/// <summary>
|
|
/// True if this has a universal selector (*)
|
|
/// </summary>
|
|
public bool UniversalSelector;
|
|
|
|
/// <summary>
|
|
/// For + combinator
|
|
/// </summary>
|
|
public bool AdjacentSibling;
|
|
|
|
/// <summary>
|
|
/// For ~ combinator
|
|
/// </summary>
|
|
public bool GeneralSibling;
|
|
|
|
public StyleSelector[] AnyOf;
|
|
public StyleSelector[] DecendantOf;
|
|
public StyleSelector[] Has;
|
|
public int SelfScore;
|
|
|
|
public Func<IStyleTarget, bool> NthChild;
|
|
|
|
public int Score
|
|
{
|
|
get
|
|
{
|
|
if ( Block != null )
|
|
{
|
|
return Block.LoadOrder + SelfScore * 1000;
|
|
}
|
|
|
|
return SelfScore;
|
|
}
|
|
}
|
|
|
|
// publuc ParentType [ Ascendant, Parent, Adjacent Sibling, General Sibling ]
|
|
|
|
internal void SetClasses( string[] classes )
|
|
{
|
|
Classes = classes;
|
|
}
|
|
|
|
public void Finalize( StyleBlock block )
|
|
{
|
|
Block = block;
|
|
|
|
UpdateScore();
|
|
}
|
|
|
|
int UpdateScore()
|
|
{
|
|
SelfScore = 0;
|
|
|
|
//
|
|
// https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance
|
|
//
|
|
|
|
// Id is the holy grail
|
|
// "The selector with the greater value in the ID column wins no matter what the values are in the other columns"
|
|
if ( Id != null ) SelfScore += 1000;
|
|
|
|
// Elements score the lowest
|
|
if ( Element != null ) SelfScore += 1;
|
|
|
|
// Each class is counted as 10
|
|
if ( Classes != null ) SelfScore += Classes.Count() * 10;
|
|
|
|
// If we have any flags, count each one as 10
|
|
if ( Flags != 0 ) SelfScore += ((int)Flags).BitsSet() * 10;
|
|
|
|
// Nth child counts as a flag
|
|
if ( NthChild != null ) SelfScore += 10;
|
|
|
|
// :not doesn't count as anything special, but we do count its content score
|
|
if ( Not != null ) SelfScore += Not.UpdateScore();
|
|
|
|
// This isn't actually perfectly accurate and you might be able to engineer weird situations
|
|
// but it should be fine 99% of the time. If it isn't then the move is to get rid of AnyOf and
|
|
// whatever and collapse the & stuff into individual rules
|
|
if ( AnyOf != null ) SelfScore += AnyOf.Max( x => x.UpdateScore() );
|
|
if ( DecendantOf != null ) SelfScore += DecendantOf.Max( x => x.UpdateScore() );
|
|
|
|
// Add our parents score
|
|
if ( Parent != null ) SelfScore += Parent.UpdateScore();
|
|
|
|
//
|
|
// You'd think things like .a > .b would score higher than .a .b, but that isn't true
|
|
//
|
|
|
|
return SelfScore;
|
|
}
|
|
|
|
public bool TestBroadphase( IStyleTarget target )
|
|
{
|
|
//
|
|
// If we have element names
|
|
//
|
|
if ( Element != null )
|
|
{
|
|
if ( target.ElementName != Element )
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Id check
|
|
//
|
|
if ( Id != null )
|
|
{
|
|
if ( target.Id == null )
|
|
return false;
|
|
|
|
if ( string.Compare( target.Id, Id, true ) != 0 )
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// If we have a class, you better have one too
|
|
//
|
|
if ( Classes != null )
|
|
{
|
|
if ( !target.HasClasses( Classes ) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test whether target passes our selector test. We use forceFlag to do alternate tests for flags like ::before and ::after.
|
|
/// It's basically added to the target's pseudo class list for the test.
|
|
/// </summary>
|
|
public bool Test( IStyleTarget target, PseudoClass forceFlag = PseudoClass.None )
|
|
{
|
|
var pseudo = target.PseudoClass | forceFlag;
|
|
|
|
// Not optional - need to match!
|
|
if ( pseudo.Contains( PseudoClass.Before ) && !Flags.Contains( PseudoClass.Before ) ) return false;
|
|
if ( pseudo.Contains( PseudoClass.After ) && !Flags.Contains( PseudoClass.After ) ) return false;
|
|
|
|
//
|
|
// If we have flags, you better match them
|
|
//
|
|
if ( Flags != PseudoClass.None && (pseudo & Flags) != Flags )
|
|
return false;
|
|
|
|
//
|
|
// :nth-child( 2 )
|
|
//
|
|
if ( NthChild != null && !NthChild( target ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( !TestBroadphase( target ) )
|
|
return false;
|
|
|
|
if ( Parent != null )
|
|
{
|
|
if ( !Parent.TestParent( target.Parent, !ImmediateParent ) )
|
|
return false;
|
|
}
|
|
|
|
if ( Has != null && Has.Length > 0 )
|
|
{
|
|
if ( !TestHas( target ) )
|
|
return false;
|
|
}
|
|
|
|
if ( DecendantOf != null )
|
|
{
|
|
bool passed = false;
|
|
foreach ( var p in DecendantOf )
|
|
{
|
|
if ( !p.TestParent( target.Parent, !ImmediateParent ) )
|
|
continue;
|
|
|
|
passed = true;
|
|
break;
|
|
}
|
|
|
|
if ( !passed ) return false;
|
|
}
|
|
|
|
if ( AnyOf != null )
|
|
{
|
|
bool passed = false;
|
|
foreach ( var p in AnyOf )
|
|
{
|
|
if ( !p.Test( target ) )
|
|
continue;
|
|
|
|
passed = true;
|
|
break;
|
|
}
|
|
|
|
if ( !passed ) return false;
|
|
}
|
|
|
|
if ( Not != null )
|
|
{
|
|
if ( Not.Test( target ) )
|
|
return false;
|
|
}
|
|
|
|
// UniversalSelector
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
public bool TestParent( IStyleTarget target, bool recusive = true )
|
|
{
|
|
if ( target == null )
|
|
return false;
|
|
|
|
if ( Test( target ) )
|
|
return true;
|
|
|
|
if ( !recusive )
|
|
return false;
|
|
|
|
return TestParent( target.Parent );
|
|
}
|
|
|
|
private bool TestHas( IStyleTarget target )
|
|
{
|
|
foreach ( var hasSelector in Has )
|
|
{
|
|
if ( hasSelector.AdjacentSibling )
|
|
{
|
|
var nextSibling = GetNextSibling( target );
|
|
|
|
if ( nextSibling != null && hasSelector.Test( nextSibling ) )
|
|
return true;
|
|
}
|
|
else if ( hasSelector.GeneralSibling )
|
|
{
|
|
var sibling = GetNextSibling( target );
|
|
|
|
while ( sibling != null )
|
|
{
|
|
if ( hasSelector.Test( sibling ) )
|
|
return true;
|
|
|
|
sibling = GetNextSibling( sibling );
|
|
}
|
|
}
|
|
else if ( hasSelector.ImmediateParent )
|
|
{
|
|
foreach ( var child in target.Children ?? Enumerable.Empty<IStyleTarget>() )
|
|
{
|
|
if ( hasSelector.Test( child ) )
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( TestDescendants( target, hasSelector ) )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool TestDescendants( IStyleTarget target, StyleSelector selector )
|
|
{
|
|
var children = target.Children;
|
|
if ( children == null ) return false;
|
|
|
|
foreach ( var child in children )
|
|
{
|
|
if ( selector.Test( child ) )
|
|
return true;
|
|
|
|
if ( TestDescendants( child, selector ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private IStyleTarget GetNextSibling( IStyleTarget target )
|
|
{
|
|
if ( target.Parent == null )
|
|
return null;
|
|
|
|
var siblings = target.Parent.Children?.ToList();
|
|
if ( siblings == null )
|
|
return null;
|
|
|
|
var index = siblings.IndexOf( target );
|
|
if ( index >= 0 && index < siblings.Count - 1 )
|
|
return siblings[index + 1];
|
|
|
|
return null;
|
|
}
|
|
}
|