Files
RobustToolbox/Robust.Client/UserInterface/Stylesheet.cs
T
Pieter-Jan Briers 13ef4e8423 Various optimizations focused on the entity spawn panel:
Give BoxContainer a preset capacity to reduce allocations in layout.

Removed Control.GetChild(string) and similar. This was no longer used (and was a pretty bad idea in general), and the implementation had a lot of wasted book keeping to do.

Use UpdateLayout() instead of DoUpdateLayout() in Control minsize changed to avoid doing redundant layout, and by extension style & minsize, updates.

Used RemoveAllChildren instead of DisposeAllChildren in EntitySpawnWindow because it's faster (way less stuff to clear, just let the GC handle it).

Optimized ResourcePath with a better GetHashCode(), better Equals, replacement for List<T>.ToArray()

Fixed enumerator allocations in stylesheet SelectorElement matching to speed up style updates.

ResourceCache now caches fallbacks to avoid constructing Resource instance when fetching fallback explicitly.

ResourceCache now has nested dictionaries for Type -> ResourcePath -> BaseResource storage of resources.

Use calculated property instead of auto property for TextureResource.Fallback
2019-11-28 14:19:02 +01:00

323 lines
9.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface
{
/// <summary>
/// As part of my evil plan to convince Monster to work on SS14, I'm porting web technologies to Robust.
/// Here's CSS style sheets.
/// </summary>
public sealed class Stylesheet
{
public IReadOnlyList<StyleRule> Rules { get; }
// More organized forms of the above rules list, that is more efficient to query.
internal List<(int order, StyleRule rule)> UnsortedRules { get; }
internal Dictionary<Type, List<(int order, StyleRule rule)>> TypeSortedRules { get; }
public Stylesheet(IReadOnlyList<StyleRule> rules)
{
Rules = rules;
UnsortedRules = new List<(int order, StyleRule rule)>();
TypeSortedRules = new Dictionary<Type, List<(int order, StyleRule rule)>>();
var order = 0;
var typeControl = typeof(Control);
foreach (var rule in rules)
{
if (rule.Selector is SelectorElement element
&& element.ElementType != null
&& typeControl.IsAssignableFrom(element.ElementType)
&& element.ElementType != typeControl)
{
if (!TypeSortedRules.TryGetValue(element.ElementType, out var typeList))
{
typeList = new List<(int order, StyleRule rule)>();
TypeSortedRules.Add(element.ElementType, typeList);
}
typeList.Add((order, rule));
}
else
{
UnsortedRules.Add((order, rule));
}
order += 1;
}
}
}
/// <summary>
/// A single rule in a style sheet, containing a bunch of properties and a selector.
/// </summary>
public sealed class StyleRule
{
public StyleRule(Selector selector, IReadOnlyList<StyleProperty> properties)
{
Selector = selector;
Properties = properties;
Specificity = selector.CalculateSpecificity();
}
public StyleSpecificity Specificity { get; }
public Selector Selector { get; }
public IReadOnlyList<StyleProperty> Properties { get; }
}
// https://specifishity.com/
public struct StyleSpecificity : IComparable<StyleSpecificity>, IComparable
{
public readonly int IdSelectors;
public readonly int ClassSelectors;
public readonly int TypeSelectors;
public StyleSpecificity(int idSelectors, int classSelectors, int typeSelectors)
{
IdSelectors = idSelectors;
ClassSelectors = classSelectors;
TypeSelectors = typeSelectors;
}
public static StyleSpecificity operator +(StyleSpecificity a, StyleSpecificity b)
{
return new StyleSpecificity(
a.IdSelectors + b.IdSelectors,
a.ClassSelectors + b.ClassSelectors,
a.TypeSelectors + b.TypeSelectors);
}
public int CompareTo(StyleSpecificity other)
{
var idSelectorsComparison = IdSelectors.CompareTo(other.IdSelectors);
if (idSelectorsComparison != 0)
{
return idSelectorsComparison;
}
var classSelectorsComparison = ClassSelectors.CompareTo(other.ClassSelectors);
if (classSelectorsComparison != 0)
{
return classSelectorsComparison;
}
return TypeSelectors.CompareTo(other.TypeSelectors);
}
public int CompareTo(object obj)
{
if (ReferenceEquals(null, obj)) return 1;
return obj is StyleSpecificity other
? CompareTo(other)
: throw new ArgumentException($"Object must be of type {nameof(StyleSpecificity)}");
}
public override string ToString()
{
return $"({IdSelectors}-{ClassSelectors}-{TypeSelectors})";
}
}
/// <summary>
/// A single property in a rule, with a name and an object value.
/// </summary>
public readonly struct StyleProperty
{
public StyleProperty(string name, object value)
{
Name = name;
Value = value;
}
public string Name { get; }
public object Value { get; }
}
public abstract class Selector
{
public abstract bool Matches(Control control);
public abstract StyleSpecificity CalculateSpecificity();
}
public sealed class SelectorElement : Selector
{
private readonly string[] _elementClasses;
private readonly string[] _pseudoClasses;
public SelectorElement(
Type elementType,
IEnumerable<string> elementClasses,
string elementId,
IEnumerable<string> pseudoClass)
{
if (elementType != null)
{
if (!typeof(Control).IsAssignableFrom(elementType))
{
throw new ArgumentException("elementType must inherit Control.", nameof(elementType));
}
if (elementType == typeof(Control))
{
throw new ArgumentException("elementType may not be Control itself.", nameof(elementType));
}
}
ElementType = elementType;
_elementClasses = elementClasses?.ToArray();
ElementId = elementId;
_pseudoClasses = pseudoClass?.ToArray();
}
public static SelectorElement Type(Type elementType)
{
return new SelectorElement(elementType, null, null, null);
}
public static SelectorElement Class(params string[] classes)
{
return new SelectorElement(null, classes, null, null);
}
public static SelectorElement Id(string id)
{
return new SelectorElement(null, null, id, null);
}
public Type ElementType { get; }
public IReadOnlyList<string> ElementClasses => _elementClasses;
public string ElementId { get; }
public IReadOnlyList<string> PseudoClasses => _pseudoClasses;
public override bool Matches(Control control)
{
if (ElementId != null && control.StyleIdentifier != ElementId)
{
return false;
}
if (ElementType != null && !ElementType.IsInstanceOfType(control))
{
return false;
}
if (_elementClasses != null)
{
foreach (var elementClass in _elementClasses)
{
if (!control.HasStyleClass(elementClass))
{
return false;
}
}
}
if (_pseudoClasses != null)
{
foreach (var elementClass in _pseudoClasses)
{
if (!control.HasStylePseudoClass(elementClass))
{
return false;
}
}
}
return true;
}
public override StyleSpecificity CalculateSpecificity()
{
var countId = ElementId == null ? 0 : 1;
var countClasses = (ElementClasses?.Count ?? 0) + (PseudoClasses?.Count ?? 0);
var countTypes = 0;
if (ElementType != null)
{
var type = ElementType;
while (type != typeof(Control))
{
DebugTools.AssertNotNull(type);
type = type.BaseType;
countTypes += 1;
}
}
return new StyleSpecificity(countId, countClasses, countTypes);
}
}
// Temporarily hidden due to performance concerns.
// Like seriously this thing is awful performance wise.
// Also, you can't just enable it. The code is here but AddChild etc only do restyles one level deep.
internal sealed class SelectorDescendant : Selector
{
public SelectorDescendant([NotNull] Selector ascendant, [NotNull] Selector descendant)
{
Ascendant = ascendant;
Descendant = descendant;
}
public Selector Ascendant { get; }
public Selector Descendant { get; }
public override bool Matches(Control control)
{
if (!Descendant.Matches(control))
{
return false;
}
var parent = control.Parent;
var found = false;
while (parent != null)
{
if (Ascendant.Matches(parent))
{
found = true;
break;
}
parent = parent.Parent;
}
return found;
}
public override StyleSpecificity CalculateSpecificity()
{
return Ascendant.CalculateSpecificity() + Descendant.CalculateSpecificity();
}
}
// Temporarily hidden due to performance concerns.
public sealed class SelectorChild : Selector
{
public SelectorChild(Selector parent, Selector child)
{
Parent = parent;
Child = child;
}
public Selector Parent { get; }
public Selector Child { get; }
public override bool Matches(Control control)
{
if (control.Parent == null)
{
return false;
}
return Parent.Matches(control.Parent) && Child.Matches(control);
}
public override StyleSpecificity CalculateSpecificity()
{
return Parent.CalculateSpecificity() + Child.CalculateSpecificity();
}
}
}