Browse Source

Merge branch 'selectors'

Michael Ragazzon 3 years ago
parent
commit
cca56fd920
36 changed files with 889 additions and 1697 deletions
  1. 2 24
      CMake/FileList.cmake
  2. 4 0
      Source/Core/Element.cpp
  3. 4 0
      Source/Core/StyleSheet.cpp
  4. 64 37
      Source/Core/StyleSheetFactory.cpp
  5. 6 7
      Source/Core/StyleSheetFactory.h
  6. 76 39
      Source/Core/StyleSheetNode.cpp
  7. 22 32
      Source/Core/StyleSheetNode.h
  8. 0 52
      Source/Core/StyleSheetNodeSelector.cpp
  9. 0 60
      Source/Core/StyleSheetNodeSelector.h
  10. 0 57
      Source/Core/StyleSheetNodeSelectorEmpty.cpp
  11. 0 53
      Source/Core/StyleSheetNodeSelectorEmpty.h
  12. 0 72
      Source/Core/StyleSheetNodeSelectorFirstChild.cpp
  13. 0 53
      Source/Core/StyleSheetNodeSelectorFirstChild.h
  14. 0 72
      Source/Core/StyleSheetNodeSelectorFirstOfType.cpp
  15. 0 53
      Source/Core/StyleSheetNodeSelectorFirstOfType.h
  16. 0 72
      Source/Core/StyleSheetNodeSelectorLastChild.cpp
  17. 0 53
      Source/Core/StyleSheetNodeSelectorLastChild.h
  18. 0 72
      Source/Core/StyleSheetNodeSelectorLastOfType.cpp
  19. 0 53
      Source/Core/StyleSheetNodeSelectorLastOfType.h
  20. 0 74
      Source/Core/StyleSheetNodeSelectorNthChild.cpp
  21. 0 53
      Source/Core/StyleSheetNodeSelectorNthChild.h
  22. 0 72
      Source/Core/StyleSheetNodeSelectorNthLastChild.cpp
  23. 0 53
      Source/Core/StyleSheetNodeSelectorNthLastChild.h
  24. 0 70
      Source/Core/StyleSheetNodeSelectorNthLastOfType.cpp
  25. 0 53
      Source/Core/StyleSheetNodeSelectorNthLastOfType.h
  26. 0 70
      Source/Core/StyleSheetNodeSelectorNthOfType.cpp
  27. 0 53
      Source/Core/StyleSheetNodeSelectorNthOfType.h
  28. 0 71
      Source/Core/StyleSheetNodeSelectorOnlyChild.cpp
  29. 0 53
      Source/Core/StyleSheetNodeSelectorOnlyChild.h
  30. 0 72
      Source/Core/StyleSheetNodeSelectorOnlyOfType.cpp
  31. 0 53
      Source/Core/StyleSheetNodeSelectorOnlyOfType.h
  32. 62 36
      Source/Core/StyleSheetParser.cpp
  33. 348 0
      Source/Core/StyleSheetSelector.cpp
  34. 118 0
      Source/Core/StyleSheetSelector.h
  35. 58 23
      Tests/Source/Benchmarks/Selectors.cpp
  36. 125 30
      Tests/Source/UnitTests/Selectors.cpp

+ 2 - 24
CMake/FileList.cmake

@@ -88,19 +88,8 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/StreamFile.h
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetFactory.h
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNode.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelector.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorEmpty.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorFirstChild.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorFirstOfType.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorLastChild.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorLastOfType.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorNthChild.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorNthLastChild.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorNthLastOfType.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorNthOfType.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorOnlyChild.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorOnlyOfType.h
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetParser.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetSelector.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Template.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TemplateCache.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TextureDatabase.h
@@ -379,19 +368,8 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetContainer.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetFactory.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNode.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelector.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorEmpty.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorFirstChild.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorFirstOfType.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorLastChild.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorLastOfType.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorNthChild.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorNthLastChild.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorNthLastOfType.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorNthOfType.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorOnlyChild.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelectorOnlyOfType.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetParser.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetSelector.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetSpecification.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/SystemInterface.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Template.cpp

+ 4 - 0
Source/Core/Element.cpp

@@ -1500,6 +1500,8 @@ static Element* QuerySelectorMatchRecursive(const StyleSheetNodeListRaw& nodes,
 	for (int i = 0; i < num_children; i++)
 	{
 		Element* child = element->GetChild(i);
+		if (child->GetTagName() == "#text")
+			continue;
 
 		for (const StyleSheetNode* node : nodes)
 		{
@@ -1522,6 +1524,8 @@ static void QuerySelectorAllMatchRecursive(ElementList& matching_elements, const
 	for (int i = 0; i < num_children; i++)
 	{
 		Element* child = element->GetChild(i);
+		if (child->GetTagName() == "#text")
+			continue;
 
 		for (const StyleSheetNode* node : nodes)
 		{

+ 4 - 0
Source/Core/StyleSheet.cpp

@@ -195,6 +195,10 @@ SharedPtr<const ElementDefinition> StyleSheet::GetElementDefinition(const Elemen
 	const String& id = element->GetId();
 	const StringList& class_names = element->GetStyle()->GetClassNameList();
 
+	// Text elements are never matched.
+	if (tag == "#text")
+		return nullptr;
+
 	// First, look up the indexed requirements. 
 	if (!id.empty())
 		AddApplicableNodes(styled_node_index.ids, id);

+ 64 - 37
Source/Core/StyleSheetFactory.cpp

@@ -15,7 +15,7 @@
  *
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -27,50 +27,40 @@
  */
 
 #include "StyleSheetFactory.h"
+#include "../../Include/RmlUi/Core/Log.h"
 #include "../../Include/RmlUi/Core/StyleSheetContainer.h"
-#include "StyleSheetNode.h"
 #include "StreamFile.h"
-#include "StyleSheetNodeSelectorNthChild.h"
-#include "StyleSheetNodeSelectorNthLastChild.h"
-#include "StyleSheetNodeSelectorNthOfType.h"
-#include "StyleSheetNodeSelectorNthLastOfType.h"
-#include "StyleSheetNodeSelectorFirstChild.h"
-#include "StyleSheetNodeSelectorLastChild.h"
-#include "StyleSheetNodeSelectorFirstOfType.h"
-#include "StyleSheetNodeSelectorLastOfType.h"
-#include "StyleSheetNodeSelectorOnlyChild.h"
-#include "StyleSheetNodeSelectorOnlyOfType.h"
-#include "StyleSheetNodeSelectorEmpty.h"
-#include "../../Include/RmlUi/Core/Log.h"
+#include "StyleSheetNode.h"
+#include "StyleSheetParser.h"
+#include "StyleSheetSelector.h"
 
 namespace Rml {
 
 static UniquePtr<StyleSheetFactory> instance;
 
-StyleSheetFactory::StyleSheetFactory()
+StyleSheetFactory::StyleSheetFactory() :
+	selectors{
+		{"nth-child", StructuralSelectorType::Nth_Child},
+		{"nth-last-child", StructuralSelectorType::Nth_Last_Child},
+		{"nth-of-type", StructuralSelectorType::Nth_Of_Type},
+		{"nth-last-of-type", StructuralSelectorType::Nth_Last_Of_Type},
+		{"first-child", StructuralSelectorType::First_Child},
+		{"last-child", StructuralSelectorType::Last_Child},
+		{"first-of-type", StructuralSelectorType::First_Of_Type},
+		{"last-of-type", StructuralSelectorType::Last_Of_Type},
+		{"only-child", StructuralSelectorType::Only_Child},
+		{"only-of-type", StructuralSelectorType::Only_Of_Type},
+		{"empty", StructuralSelectorType::Empty},
+		{"not", StructuralSelectorType::Not},
+	}
 {}
 
-StyleSheetFactory::~StyleSheetFactory()
-{}
+StyleSheetFactory::~StyleSheetFactory() {}
 
 bool StyleSheetFactory::Initialise()
 {
 	RMLUI_ASSERT(instance == nullptr);
-
 	instance = UniquePtr<StyleSheetFactory>(new StyleSheetFactory);
-
-	instance->selectors["nth-child"] = MakeUnique<StyleSheetNodeSelectorNthChild>();
-	instance->selectors["nth-last-child"] = MakeUnique<StyleSheetNodeSelectorNthLastChild>();
-	instance->selectors["nth-of-type"] = MakeUnique<StyleSheetNodeSelectorNthOfType>();
-	instance->selectors["nth-last-of-type"] = MakeUnique<StyleSheetNodeSelectorNthLastOfType>();
-	instance->selectors["first-child"] = MakeUnique<StyleSheetNodeSelectorFirstChild>();
-	instance->selectors["last-child"] = MakeUnique<StyleSheetNodeSelectorLastChild>();
-	instance->selectors["first-of-type"] = MakeUnique<StyleSheetNodeSelectorFirstOfType>();
-	instance->selectors["last-of-type"] = MakeUnique<StyleSheetNodeSelectorLastOfType>();
-	instance->selectors["only-child"] = MakeUnique<StyleSheetNodeSelectorOnlyChild>();
-	instance->selectors["only-of-type"] = MakeUnique<StyleSheetNodeSelectorOnlyOfType>();
-	instance->selectors["empty"] = MakeUnique<StyleSheetNodeSelectorEmpty>();
-
 	return true;
 }
 
@@ -117,17 +107,54 @@ StructuralSelector StyleSheetFactory::GetSelector(const String& name)
 		it = instance->selectors.find(name.substr(0, parameter_start));
 
 	if (it == instance->selectors.end())
-		return StructuralSelector(nullptr, 0, 0);
+		return StructuralSelector(StructuralSelectorType::Invalid, 0, 0);
+
+	const StructuralSelectorType selector_type = it->second;
+
+	bool requires_parameter = false;
+	switch (selector_type)
+	{
+	case StructuralSelectorType::Nth_Child:
+	case StructuralSelectorType::Nth_Last_Child:
+	case StructuralSelectorType::Nth_Of_Type:
+	case StructuralSelectorType::Nth_Last_Of_Type:
+	case StructuralSelectorType::Not:
+		requires_parameter = true;
+		break;
+	default:
+		break;
+	}
+
+	const size_t parameter_end = name.rfind(')');
+	const bool has_parameter = (parameter_start != String::npos && parameter_end != String::npos && parameter_start < parameter_end);
+
+	if (requires_parameter != has_parameter)
+	{
+		Log::Message(Log::LT_WARNING, "Invalid selector ':%s' encountered, expected %s parameters", name.c_str(),
+			requires_parameter ? "parenthesized" : "no");
+		return StructuralSelector(StructuralSelectorType::Invalid, 0, 0);
+	}
 
 	// Parse the 'a' and 'b' values.
 	int a = 1;
 	int b = 0;
 
-	const size_t parameter_end = name.find(')', parameter_start + 1);
-	if (parameter_start != String::npos &&
-		parameter_end != String::npos)
+	if (has_parameter)
 	{
-		String parameters = StringUtilities::StripWhitespace(name.substr(parameter_start + 1, parameter_end - (parameter_start + 1)));
+		const String parameters = StringUtilities::StripWhitespace(StringView(name, parameter_start + 1, parameter_end - (parameter_start + 1)));
+
+		if (selector_type == StructuralSelectorType::Not)
+		{
+			auto list = MakeShared<SelectorTree>();
+			list->root = MakeUnique<StyleSheetNode>();
+			list->leafs = StyleSheetParser::ConstructNodes(*list->root, parameters);
+
+			int specificity = 0;
+			for (const StyleSheetNode* node : list->leafs)
+				specificity = Math::Max(specificity, node->GetSpecificity());
+
+			return StructuralSelector(selector_type, std::move(list), specificity);
+		}
 
 		// Check for 'even' or 'odd' first.
 		if (parameters == "even")
@@ -181,7 +208,7 @@ StructuralSelector StyleSheetFactory::GetSelector(const String& name)
 		}
 	}
 
-	return StructuralSelector(it->second.get(), a, b);
+	return StructuralSelector(selector_type, a, b);
 }
 
 UniquePtr<const StyleSheetContainer> StyleSheetFactory::LoadStyleSheetContainer(const String& sheet)

+ 6 - 7
Source/Core/StyleSheetFactory.h

@@ -15,7 +15,7 @@
  *
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -34,17 +34,16 @@
 namespace Rml {
 
 class StyleSheetContainer;
-class StyleSheetNodeSelector;
+enum class StructuralSelectorType;
 struct StructuralSelector;
 
 /**
-	Creates stylesheets on the fly as needed. The factory keeps a cache of built sheets for optimisation.
+    Creates stylesheets on the fly as needed. The factory keeps a cache of built sheets for optimisation.
 
-	@author Lloyd Weehuizen
+    @author Lloyd Weehuizen
  */
 
-class StyleSheetFactory
-{
+class StyleSheetFactory {
 public:
 	~StyleSheetFactory();
 
@@ -77,7 +76,7 @@ private:
 	StyleSheets stylesheets;
 
 	// Custom complex selectors available for style sheets.
-	using SelectorMap = UnorderedMap<String, UniquePtr<StyleSheetNodeSelector>>;
+	using SelectorMap = UnorderedMap<String, StructuralSelectorType>;
 	SelectorMap selectors;
 };
 

+ 76 - 39
Source/Core/StyleSheetNode.cpp

@@ -15,7 +15,7 @@
  *
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -31,24 +31,34 @@
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "StyleSheetFactory.h"
-#include "StyleSheetNodeSelector.h"
+#include "StyleSheetSelector.h"
 #include <algorithm>
 
 namespace Rml {
 
+static inline bool IsTextElement(const Element* element)
+{
+	return element->GetTagName() == "#text";
+}
+
 StyleSheetNode::StyleSheetNode()
 {
 	CalculateAndSetSpecificity();
 }
 
-StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, const String& tag, const String& id, const StringList& classes, const StringList& pseudo_classes, const StructuralSelectorList& structural_selectors, bool child_combinator)
-	: parent(parent), tag(tag), id(id), class_names(classes), pseudo_class_names(pseudo_classes), structural_selectors(structural_selectors), child_combinator(child_combinator)
+StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, const String& tag, const String& id, const StringList& classes,
+	const StringList& pseudo_classes, const StructuralSelectorList& structural_selectors, SelectorCombinator combinator) :
+	parent(parent),
+	tag(tag), id(id), class_names(classes), pseudo_class_names(pseudo_classes), structural_selectors(structural_selectors), combinator(combinator)
 {
 	CalculateAndSetSpecificity();
 }
 
-StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator)
-	: parent(parent), tag(std::move(tag)), id(std::move(id)), class_names(std::move(classes)), pseudo_class_names(std::move(pseudo_classes)), structural_selectors(std::move(structural_selectors)), child_combinator(child_combinator)
+StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes,
+	StructuralSelectorList&& structural_selectors, SelectorCombinator combinator) :
+	parent(parent),
+	tag(std::move(tag)), id(std::move(id)), class_names(std::move(classes)), pseudo_class_names(std::move(pseudo_classes)),
+	structural_selectors(std::move(structural_selectors)), combinator(combinator)
 {
 	CalculateAndSetSpecificity();
 }
@@ -58,12 +68,13 @@ StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(const StyleSheetNode& other
 	// See if we match the target child
 	for (const auto& child : children)
 	{
-		if (child->EqualRequirements(other.tag, other.id, other.class_names, other.pseudo_class_names, other.structural_selectors, other.child_combinator))
+		if (child->EqualRequirements(other.tag, other.id, other.class_names, other.pseudo_class_names, other.structural_selectors, other.combinator))
 			return child.get();
 	}
 
 	// We don't, so create a new child
-	auto child = MakeUnique<StyleSheetNode>(this, other.tag, other.id, other.class_names, other.pseudo_class_names, other.structural_selectors, other.child_combinator);
+	auto child = MakeUnique<StyleSheetNode>(this, other.tag, other.id, other.class_names, other.pseudo_class_names, other.structural_selectors,
+		other.combinator);
 	StyleSheetNode* result = child.get();
 
 	children.push_back(std::move(child));
@@ -71,17 +82,19 @@ StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(const StyleSheetNode& other
 	return result;
 }
 
-StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_pseudo_classes, bool child_combinator)
+StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes,
+	StructuralSelectorList&& structural_pseudo_classes, SelectorCombinator combinator)
 {
 	// See if we match an existing child
 	for (const auto& child : children)
 	{
-		if (child->EqualRequirements(tag, id, classes, pseudo_classes, structural_pseudo_classes, child_combinator))
+		if (child->EqualRequirements(tag, id, classes, pseudo_classes, structural_pseudo_classes, combinator))
 			return child.get();
 	}
 
 	// We don't, so create a new child
-	auto child = MakeUnique<StyleSheetNode>(this, std::move(tag), std::move(id), std::move(classes), std::move(pseudo_classes), std::move(structural_pseudo_classes), child_combinator);
+	auto child = MakeUnique<StyleSheetNode>(this, std::move(tag), std::move(id), std::move(classes), std::move(pseudo_classes),
+		std::move(structural_pseudo_classes), combinator);
 	StyleSheetNode* result = child.get();
 
 	children.push_back(std::move(child));
@@ -108,11 +121,11 @@ UniquePtr<StyleSheetNode> StyleSheetNode::DeepCopy(StyleSheetNode* in_parent) co
 {
 	RMLUI_ZoneScoped;
 
-	auto node = MakeUnique<StyleSheetNode>(in_parent, tag, id, class_names, pseudo_class_names, structural_selectors, child_combinator);
+	auto node = MakeUnique<StyleSheetNode>(in_parent, tag, id, class_names, pseudo_class_names, structural_selectors, combinator);
 
 	node->properties = properties;
 	node->children.resize(children.size());
-	
+
 	for (size_t i = 0; i < children.size(); i++)
 	{
 		node->children[i] = children[i]->DeepCopy(node.get());
@@ -178,7 +191,8 @@ bool StyleSheetNode::SetStructurallyVolatileRecursive(bool ancestor_is_structura
 	return (self_is_structural_pseudo_class || descendant_is_structural_pseudo_class);
 }
 
-bool StyleSheetNode::EqualRequirements(const String& _tag, const String& _id, const StringList& _class_names, const StringList& _pseudo_class_names, const StructuralSelectorList& _structural_selectors, bool _child_combinator) const
+bool StyleSheetNode::EqualRequirements(const String& _tag, const String& _id, const StringList& _class_names, const StringList& _pseudo_class_names,
+	const StructuralSelectorList& _structural_selectors, SelectorCombinator _combinator) const
 {
 	if (tag != _tag)
 		return false;
@@ -190,7 +204,7 @@ bool StyleSheetNode::EqualRequirements(const String& _tag, const String& _id, co
 		return false;
 	if (structural_selectors != _structural_selectors)
 		return false;
-	if (child_combinator != _child_combinator)
+	if (combinator != _combinator)
 		return false;
 
 	return true;
@@ -215,7 +229,7 @@ const PropertyDictionary& StyleSheetNode::GetProperties() const
 	return properties;
 }
 
-inline bool StyleSheetNode::Match(const Element* element) const
+bool StyleSheetNode::Match(const Element* element) const
 {
 	if (!tag.empty() && tag != element->GetTagName())
 		return false;
@@ -253,19 +267,20 @@ inline bool StyleSheetNode::MatchStructuralSelector(const Element* element) cons
 {
 	for (auto& node_selector : structural_selectors)
 	{
-		if (!node_selector.selector->IsApplicable(element, node_selector.a, node_selector.b))
+		if (!IsSelectorApplicable(element, node_selector))
 			return false;
 	}
-	
+
 	return true;
 }
 
-// Returns true if this node is applicable to the given element, given its IDs, classes and heritage.
 bool StyleSheetNode::IsApplicable(const Element* const in_element) const
 {
-	// Determine whether the element matches the current node and its entire lineage. The entire hierarchy of
-	// the element's document will be considered during the match as necessary.
+	// Determine whether the element matches the current node and its entire lineage. The entire hierarchy of the element's document will be
+	// considered during the match as necessary.
 
+	// We could in principle just call Match() here and then go on with the ancestor style nodes. Instead, we test the requirements of this node in a
+	// particular order for performance reasons .
 	for (const String& name : pseudo_class_names)
 	{
 		if (!in_element->IsPseudoClassSet(name))
@@ -286,17 +301,39 @@ bool StyleSheetNode::IsApplicable(const Element* const in_element) const
 
 	const Element* element = in_element;
 
-	// Walk up through all our parent nodes, each one of them must be matched by some ancestor element.
-	for(const StyleSheetNode* node = parent; node && node->parent; node = node->parent)
+	// Walk up through all our parent nodes, each one of them must be matched by some ancestor or sibling element.
+	for (const StyleSheetNode* node = parent; node && node->parent; node = node->parent)
 	{
-		// Try a match on every element ancestor. If it succeeds, we continue on to the next node.
-		for(element = element->GetParentNode(); element; element = element->GetParentNode())
+		switch (node->combinator)
+		{
+		case SelectorCombinator::None:
+		case SelectorCombinator::Child:
+		{
+			// Try a match on every element ancestor. If it succeeds, we continue on to the next node.
+			for (element = element->GetParentNode(); element; element = element->GetParentNode())
+			{
+				if (node->Match(element))
+					break;
+				// If the node has a child combinator we must match this first ancestor.
+				else if (node->combinator == SelectorCombinator::Child)
+					return false;
+			}
+		}
+		break;
+		case SelectorCombinator::NextSibling:
+		case SelectorCombinator::SubsequentSibling:
 		{
-			if (node->Match(element))
-				break;
-			// If we have a child combinator on the node, we must match this first ancestor.
-			else if (node->child_combinator)
-				return false;
+			// Try a match on every element ancestor. If it succeeds, we continue on to the next node.
+			for (element = element->GetPreviousSibling(); element; element = element->GetPreviousSibling())
+			{
+				if (node->Match(element) && !IsTextElement(in_element))
+					break;
+				// If the node has a next-sibling combinator we must match this first sibling.
+				else if (node->combinator == SelectorCombinator::NextSibling && !IsTextElement(in_element))
+					return false;
+			}
+		}
+		break;
 		}
 
 		// We have run out of element ancestors before we matched every node. Bail out.
@@ -316,24 +353,24 @@ bool StyleSheetNode::IsStructurallyVolatile() const
 	return is_structurally_volatile;
 }
 
-
 void StyleSheetNode::CalculateAndSetSpecificity()
 {
-	// Calculate the specificity of just this node; tags are worth 10,000, IDs 1,000,000 and other specifiers (classes
-	// and pseudo-classes) 100,000.
+	// First calculate the specificity of this node alone.
 	specificity = 0;
 
 	if (!tag.empty())
-		specificity += 10'000;
+		specificity += SelectorSpecificity::Tag;
 
 	if (!id.empty())
-		specificity += 1'000'000;
+		specificity += SelectorSpecificity::ID;
 
-	specificity += 100'000*(int)class_names.size();
-	specificity += 100'000*(int)pseudo_class_names.size();
-	specificity += 100'000*(int)structural_selectors.size();
+	specificity += SelectorSpecificity::Class * (int)class_names.size();
+	specificity += SelectorSpecificity::PseudoClass * (int)pseudo_class_names.size();
+	
+	for (const StructuralSelector& selector : structural_selectors)
+		specificity += selector.specificity; 
 
-	// Add our parent's specificity onto ours.
+	// Then add our parent's specificity onto ours.
 	if (parent)
 		specificity += parent->specificity;
 }

+ 22 - 32
Source/Core/StyleSheetNode.h

@@ -15,7 +15,7 @@
  *
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -31,42 +31,32 @@
 
 #include "../../Include/RmlUi/Core/PropertyDictionary.h"
 #include "../../Include/RmlUi/Core/Types.h"
-#include <tuple>
+#include "StyleSheetSelector.h"
 
 namespace Rml {
 
 struct StyleSheetIndex;
 class StyleSheetNode;
-class StyleSheetNodeSelector;
-
-struct StructuralSelector {
-	StructuralSelector(StyleSheetNodeSelector* selector, int a, int b) : selector(selector), a(a), b(b) {}
-	StyleSheetNodeSelector* selector;
-	int a;
-	int b;
-};
-inline bool operator==(const StructuralSelector& a, const StructuralSelector& b) { return a.selector == b.selector && a.a == b.a && a.b == b.b; }
-inline bool operator<(const StructuralSelector& a, const StructuralSelector& b) { return std::tie(a.selector, a.a, a.b) < std::tie(b.selector, b.a, b.b); }
-
-using StructuralSelectorList = Vector< StructuralSelector >;
-using StyleSheetNodeList = Vector< UniquePtr<StyleSheetNode> >;
-
+using StructuralSelectorList = Vector<StructuralSelector>;
+using StyleSheetNodeList = Vector<UniquePtr<StyleSheetNode>>;
 
 /**
-	A style sheet is composed of a tree of nodes.
+    A style sheet is composed of a tree of nodes.
 
-	@author Pete / Lloyd
+    @author Pete / Lloyd
  */
 
-class StyleSheetNode
-{
+class StyleSheetNode {
 public:
 	StyleSheetNode();
-	StyleSheetNode(StyleSheetNode* parent, const String& tag, const String& id, const StringList& classes, const StringList& pseudo_classes, const StructuralSelectorList& structural_selectors, bool child_combinator);
-	StyleSheetNode(StyleSheetNode* parent, String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator);
+	StyleSheetNode(StyleSheetNode* parent, const String& tag, const String& id, const StringList& classes, const StringList& pseudo_classes,
+		const StructuralSelectorList& structural_selectors, SelectorCombinator combinator);
+	StyleSheetNode(StyleSheetNode* parent, String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes,
+		StructuralSelectorList&& structural_selectors, SelectorCombinator combinator);
 
 	/// Retrieves a child node with the given requirements if they match an existing node, or else creates a new one.
-	StyleSheetNode* GetOrCreateChildNode(String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator);
+	StyleSheetNode* GetOrCreateChildNode(String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes,
+		StructuralSelectorList&& structural_selectors, SelectorCombinator combinator);
 	/// Retrieves or creates a child node with requirements equivalent to the 'other' node.
 	StyleSheetNode* GetOrCreateChildNode(const StyleSheetNode& other);
 
@@ -79,9 +69,8 @@ public:
 	/// Builds up a style sheet's index recursively.
 	void BuildIndex(StyleSheetIndex& styled_node_index) const;
 
-	/// Imports properties from a single rule definition into the node's properties and sets the
-	/// appropriate specificity on them. Any existing attributes sharing a key with a new attribute
-	/// will be overwritten if they are of a lower specificity.
+	/// Imports properties from a single rule definition into the node's properties and sets the appropriate specificity on them. Any existing
+	/// attributes sharing a key with a new attribute will be overwritten if they are of a lower specificity.
 	/// @param[in] properties The properties to import.
 	/// @param[in] rule_specificity The specificity of the importing rule.
 	void ImportProperties(const PropertyDictionary& properties, int rule_specificity);
@@ -89,18 +78,20 @@ public:
 	const PropertyDictionary& GetProperties() const;
 
 	/// Returns true if this node is applicable to the given element, given its IDs, classes and heritage.
+	/// @note For performance reasons this call does not check whether 'element' is a text element. The caller must manually check this condition and
+	/// consider any text element not applicable.
 	bool IsApplicable(const Element* element) const;
 
 	/// Returns the specificity of this node.
 	int GetSpecificity() const;
-	/// Returns true if this node employs a structural selector, and therefore generates element definitions that are
-	/// sensitive to sibling changes. 
+	/// Returns true if this node employs a structural selector, and therefore generates element definitions that are sensitive to sibling changes.
 	/// @warning Result is only valid if structural volatility is set since any changes to the node tree.
 	bool IsStructurallyVolatile() const;
 
 private:
 	// Returns true if the requirements of this node equals the given arguments.
-	bool EqualRequirements(const String& tag, const String& id, const StringList& classes, const StringList& pseudo_classes, const StructuralSelectorList& structural_pseudo_classes, bool child_combinator) const;
+	bool EqualRequirements(const String& tag, const String& id, const StringList& classes, const StringList& pseudo_classes,
+		const StructuralSelectorList& structural_pseudo_classes, SelectorCombinator combinator) const;
 
 	void CalculateAndSetSpecificity();
 
@@ -118,13 +109,12 @@ private:
 	StringList class_names;
 	StringList pseudo_class_names;
 	StructuralSelectorList structural_selectors; // Represents structural pseudo classes
-	bool child_combinator = false; // The '>' combinator: This node only matches if the element is a parent of the previous matching element.
+	SelectorCombinator combinator = SelectorCombinator::None;
 
 	// True if any ancestor, descendent, or self is a structural pseudo class.
 	bool is_structurally_volatile = true;
 
-	// A measure of specificity of this node; the attribute in a node with a higher value will override those of a
-	// node with a lower value.
+	// A measure of specificity of this node; the attribute in a node with a higher value will override those of a node with a lower value.
 	int specificity = 0;
 
 	PropertyDictionary properties;

+ 0 - 52
Source/Core/StyleSheetNodeSelector.cpp

@@ -1,52 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "StyleSheetNodeSelector.h"
-
-namespace Rml {
-
-StyleSheetNodeSelector::StyleSheetNodeSelector()
-{
-}
-
-StyleSheetNodeSelector::~StyleSheetNodeSelector()
-{
-}
-
-// Returns true if a positive integer can be found for n in the equation an + b = count.
-bool StyleSheetNodeSelector::IsNth(int a, int b, int count)
-{
-	int x = count;
-	x -= b;
-	if (a != 0)
-		x /= a;
-
-	return (x >= 0 && x * a + b == count);
-}
-
-} // namespace Rml

+ 0 - 60
Source/Core/StyleSheetNodeSelector.h

@@ -1,60 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_STYLESHEETNODESELECTOR_H
-#define RMLUI_CORE_STYLESHEETNODESELECTOR_H
-
-namespace Rml {
-
-class Element;
-
-/**
-	The ABC for any complex node selector, such as structural selectors.
-
-	@author Peter Curry
- */
-
-class StyleSheetNodeSelector
-{
-public:
-	StyleSheetNodeSelector();
-	virtual ~StyleSheetNodeSelector();
-
-	/// Returns true if the the node this selector is discriminating for is applicable to a given element.
-	/// @param element[in] The element to determine node applicability for.
-	/// @param a[in] For counting selectors, this is the 'a' variable of an + b.
-	/// @param b[in] For counting selectors, this is the 'b' variable of an + b.
-	virtual bool IsApplicable(const Element* element, int a, int b) = 0;
-
-protected:
-	/// Returns true if a positive integer can be found for n in the equation an + b = count.
-	bool IsNth(int a, int b, int count);
-};
-
-} // namespace Rml
-#endif

+ 0 - 57
Source/Core/StyleSheetNodeSelectorEmpty.cpp

@@ -1,57 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "StyleSheetNodeSelectorEmpty.h"
-#include "../../Include/RmlUi/Core/ElementText.h"
-
-namespace Rml {
-
-StyleSheetNodeSelectorEmpty::StyleSheetNodeSelectorEmpty()
-{
-}
-
-StyleSheetNodeSelectorEmpty::~StyleSheetNodeSelectorEmpty()
-{
-}
-
-// Returns true if the element has no DOM children.
-bool StyleSheetNodeSelectorEmpty::IsApplicable(const Element* element, int RMLUI_UNUSED_PARAMETER(a), int RMLUI_UNUSED_PARAMETER(b))
-{
-	RMLUI_UNUSED(a);
-	RMLUI_UNUSED(b);
-
-	for (int i = 0; i < element->GetNumChildren(); ++i)
-	{
-		if (element->GetChild(i)->GetDisplay() != Style::Display::None)
-			return false;
-	}
-
-	return true;
-}
-
-} // namespace Rml

+ 0 - 53
Source/Core/StyleSheetNodeSelectorEmpty.h

@@ -1,53 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_STYLESHEETNODESELECTOREMPTY_H
-#define RMLUI_CORE_STYLESHEETNODESELECTOREMPTY_H
-
-#include "StyleSheetNodeSelector.h"
-
-namespace Rml {
-
-/**
-	A node selector for an empty node.
-
-	@author Peter Curry
- */
-
-class StyleSheetNodeSelectorEmpty : public StyleSheetNodeSelector
-{
-public:
-	StyleSheetNodeSelectorEmpty();
-	virtual ~StyleSheetNodeSelectorEmpty();
-
-	// Returns true if the element has no DOM children.
-	bool IsApplicable(const Element* element, int a, int b) override;
-};
-
-} // namespace Rml
-#endif

+ 0 - 72
Source/Core/StyleSheetNodeSelectorFirstChild.cpp

@@ -1,72 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "StyleSheetNodeSelectorFirstChild.h"
-#include "../../Include/RmlUi/Core/ElementText.h"
-
-namespace Rml {
-
-StyleSheetNodeSelectorFirstChild::StyleSheetNodeSelectorFirstChild()
-{
-}
-
-StyleSheetNodeSelectorFirstChild::~StyleSheetNodeSelectorFirstChild()
-{
-}
-
-// Returns true if the element is the first DOM child in its parent.
-bool StyleSheetNodeSelectorFirstChild::IsApplicable(const Element* element, int RMLUI_UNUSED_PARAMETER(a), int RMLUI_UNUSED_PARAMETER(b))
-{
-	RMLUI_UNUSED(a);
-	RMLUI_UNUSED(b);
-
-	Element* parent = element->GetParentNode();
-	if (parent == nullptr)
-		return false;
-
-	int child_index = 0;
-	while (child_index < parent->GetNumChildren())
-	{
-		// If this child (the first non-text child) is our element, then the selector succeeds.
-		Element* child = parent->GetChild(child_index);
-		if (child == element)
-			return true;
-
-		// If this child is not a text element, then the selector fails; this element is non-trivial.
-		if (rmlui_dynamic_cast< ElementText* >(child) == nullptr &&
-			child->GetDisplay() != Style::Display::None)
-			return false;
-
-		// Otherwise, skip over the text element to find the last non-trivial element.
-		child_index++;
-	}
-
-	return false;
-}
-
-} // namespace Rml

+ 0 - 53
Source/Core/StyleSheetNodeSelectorFirstChild.h

@@ -1,53 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_STYLESHEETNODESELECTORFIRSTCHILD_H
-#define RMLUI_CORE_STYLESHEETNODESELECTORFIRSTCHILD_H
-
-#include "StyleSheetNodeSelector.h"
-
-namespace Rml {
-
-/**
-	A node selector for the first generic child.
-
-	@author Peter Curry
- */
-
-class StyleSheetNodeSelectorFirstChild : public StyleSheetNodeSelector
-{
-public:
-	StyleSheetNodeSelectorFirstChild();
-	virtual ~StyleSheetNodeSelectorFirstChild();
-
-	// Returns true if the element is the first DOM child in its parent.
-	bool IsApplicable(const Element* element, int a, int b) override;
-};
-
-} // namespace Rml
-#endif

+ 0 - 72
Source/Core/StyleSheetNodeSelectorFirstOfType.cpp

@@ -1,72 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "StyleSheetNodeSelectorFirstOfType.h"
-#include "../../Include/RmlUi/Core/Element.h"
-
-namespace Rml {
-
-StyleSheetNodeSelectorFirstOfType::StyleSheetNodeSelectorFirstOfType()
-{
-}
-
-StyleSheetNodeSelectorFirstOfType::~StyleSheetNodeSelectorFirstOfType()
-{
-}
-
-// Returns true if the element is the first DOM child in its parent of its type.
-bool StyleSheetNodeSelectorFirstOfType::IsApplicable(const Element* element, int RMLUI_UNUSED_PARAMETER(a), int RMLUI_UNUSED_PARAMETER(b))
-{
-	RMLUI_UNUSED(a);
-	RMLUI_UNUSED(b);
-
-	Element* parent = element->GetParentNode();
-	if (parent == nullptr)
-		return false;
-
-	int child_index = 0;
-	while (child_index < parent->GetNumChildren())
-	{
-		// If this child is our element, then it's the first one we've found with our tag; the selector succeeds.
-		Element* child = parent->GetChild(child_index);
-		if (child == element)
-			return true;
-
-		// Otherwise, if this child shares our element's tag, then our element is not the first tagged child; the
-		// selector fails.
-		if (child->GetTagName() == element->GetTagName() &&
-			child->GetDisplay() != Style::Display::None)
-			return false;
-
-		child_index++;
-	}
-
-	return false;
-}
-
-} // namespace Rml

+ 0 - 53
Source/Core/StyleSheetNodeSelectorFirstOfType.h

@@ -1,53 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_STYLESHEETNODESELECTORFIRSTOFTYPE_H
-#define RMLUI_CORE_STYLESHEETNODESELECTORFIRSTOFTYPE_H
-
-#include "StyleSheetNodeSelector.h"
-
-namespace Rml {
-
-/**
-	A node selector for the first child of its type.
-
-	@author Peter Curry
- */
-
-class StyleSheetNodeSelectorFirstOfType : public StyleSheetNodeSelector
-{
-public:
-	StyleSheetNodeSelectorFirstOfType();
-	virtual ~StyleSheetNodeSelectorFirstOfType();
-
-	/// Returns true if the element is the first DOM child in its parent of its type.
-	bool IsApplicable(const Element* element, int a, int b) override;
-};
-
-} // namespace Rml
-#endif

+ 0 - 72
Source/Core/StyleSheetNodeSelectorLastChild.cpp

@@ -1,72 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "StyleSheetNodeSelectorLastChild.h"
-#include "../../Include/RmlUi/Core/ElementText.h"
-
-namespace Rml {
-
-StyleSheetNodeSelectorLastChild::StyleSheetNodeSelectorLastChild()
-{
-}
-
-StyleSheetNodeSelectorLastChild::~StyleSheetNodeSelectorLastChild()
-{
-}
-
-// Returns true if the element is the last DOM child in its parent.
-bool StyleSheetNodeSelectorLastChild::IsApplicable(const Element* element, int RMLUI_UNUSED_PARAMETER(a), int RMLUI_UNUSED_PARAMETER(b))
-{
-	RMLUI_UNUSED(a);
-	RMLUI_UNUSED(b);
-
-	Element* parent = element->GetParentNode();
-	if (parent == nullptr)
-		return false;
-
-	int child_index = parent->GetNumChildren() - 1;
-	while (child_index >= 0)
-	{
-		// If this child (the last non-text child) is our element, then the selector succeeds.
-		Element* child = parent->GetChild(child_index);
-		if (child == element)
-			return true;
-
-		// If this child is not a text element, then the selector fails; this element is non-trivial.
-		if (rmlui_dynamic_cast< ElementText* >(child) == nullptr &&
-			child->GetDisplay() != Style::Display::None)
-			return false;
-
-		// Otherwise, skip over the text element to find the last non-trivial element.
-		child_index--;
-	}
-
-	return false;
-}
-
-} // namespace Rml

+ 0 - 53
Source/Core/StyleSheetNodeSelectorLastChild.h

@@ -1,53 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_STYLESHEETNODESELECTORLASTCHILD_H
-#define RMLUI_CORE_STYLESHEETNODESELECTORLASTCHILD_H
-
-#include "StyleSheetNodeSelector.h"
-
-namespace Rml {
-
-/**
-	A node selector for the last generic child.
-
-	@author Peter Curry
- */
-
-class StyleSheetNodeSelectorLastChild : public StyleSheetNodeSelector
-{
-public:
-	StyleSheetNodeSelectorLastChild();
-	virtual ~StyleSheetNodeSelectorLastChild();
-
-	// Returns true if the element is the last DOM child in its parent.
-	bool IsApplicable(const Element* element, int a, int b) override;
-};
-
-} // namespace Rml
-#endif

+ 0 - 72
Source/Core/StyleSheetNodeSelectorLastOfType.cpp

@@ -1,72 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "StyleSheetNodeSelectorLastOfType.h"
-#include "../../Include/RmlUi/Core/Element.h"
-
-namespace Rml {
-
-StyleSheetNodeSelectorLastOfType::StyleSheetNodeSelectorLastOfType()
-{
-}
-
-StyleSheetNodeSelectorLastOfType::~StyleSheetNodeSelectorLastOfType()
-{
-}
-
-// Returns true if the element is the last DOM child in its parent.
-bool StyleSheetNodeSelectorLastOfType::IsApplicable(const Element* element, int RMLUI_UNUSED_PARAMETER(a), int RMLUI_UNUSED_PARAMETER(b))
-{
-	RMLUI_UNUSED(a);
-	RMLUI_UNUSED(b);
-
-	Element* parent = element->GetParentNode();
-	if (parent == nullptr)
-		return false;
-
-	int child_index = parent->GetNumChildren() - 1;
-	while (child_index >= 0)
-	{
-		// If this child is our element, then it's the first one we've found with our tag; the selector succeeds.
-		Element* child = parent->GetChild(child_index);
-		if (child == element)
-			return true;
-
-		// Otherwise, if this child shares our element's tag, then our element is not the first tagged child; the
-		// selector fails.
-		if (child->GetTagName() == element->GetTagName() &&
-			child->GetDisplay() != Style::Display::None)
-			return false;
-
-		child_index--;
-	}
-
-	return false;
-}
-
-} // namespace Rml

+ 0 - 53
Source/Core/StyleSheetNodeSelectorLastOfType.h

@@ -1,53 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_STYLESHEETNODESELECTORLASTOFTYPE_H
-#define RMLUI_CORE_STYLESHEETNODESELECTORLASTOFTYPE_H
-
-#include "StyleSheetNodeSelector.h"
-
-namespace Rml {
-
-/**
-	A node selector for the first child of its type.
-
-	@author Peter Curry
- */
-
-class StyleSheetNodeSelectorLastOfType : public StyleSheetNodeSelector
-{
-public:
-	StyleSheetNodeSelectorLastOfType();
-	virtual ~StyleSheetNodeSelectorLastOfType();
-
-	// Returns true if the element is the last DOM child in its parent.
-	bool IsApplicable(const Element* element, int a, int b) override;
-};
-
-} // namespace Rml
-#endif

+ 0 - 74
Source/Core/StyleSheetNodeSelectorNthChild.cpp

@@ -1,74 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "StyleSheetNodeSelectorNthChild.h"
-#include "../../Include/RmlUi/Core/ElementText.h"
-#include "../../Include/RmlUi/Core/Log.h"
-
-namespace Rml {
-
-StyleSheetNodeSelectorNthChild::StyleSheetNodeSelectorNthChild()
-{
-}
-
-StyleSheetNodeSelectorNthChild::~StyleSheetNodeSelectorNthChild()
-{
-}
-
-// Returns true if the element index is (n * a) + b for a given integer value of n.
-bool StyleSheetNodeSelectorNthChild::IsApplicable(const Element* element, int a, int b)
-{
-	Element* parent = element->GetParentNode();
-	if (parent == nullptr)
-		return false;
-
-	// Start counting elements until we find this one.
-	int element_index = 1;
-	for (int i = 0; i < parent->GetNumChildren(); i++)
-	{
-		Element* child = parent->GetChild(i);
-
-		// Skip text nodes.
-		if (rmlui_dynamic_cast< ElementText* >(child) != nullptr)
-			continue;
-
-		// If we've found our element, then break; the current index is our element's index.
-		if (child == element)
-			break;
-
-		// Skip nodes without a display type.
-		if (child->GetDisplay() == Style::Display::None)
-			continue;
-
-		element_index++;
-	}
-
-	return IsNth(a, b, element_index);
-}
-
-} // namespace Rml

+ 0 - 53
Source/Core/StyleSheetNodeSelectorNthChild.h

@@ -1,53 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_STYLESHEETNODESELECTORNTHCHILD_H
-#define RMLUI_CORE_STYLESHEETNODESELECTORNTHCHILD_H
-
-#include "StyleSheetNodeSelector.h"
-
-namespace Rml {
-
-/**
-	A node selector for the nth generic child.
-
-	@author Peter Curry
- */
-
-class StyleSheetNodeSelectorNthChild : public StyleSheetNodeSelector
-{
-public:
-	StyleSheetNodeSelectorNthChild();
-	virtual ~StyleSheetNodeSelectorNthChild();
-
-	// Returns true if the element index is (n * a) + b for a given integer value of n.
-	bool IsApplicable(const Element* element, int a, int b) override;
-};
-
-} // namespace Rml
-#endif

+ 0 - 72
Source/Core/StyleSheetNodeSelectorNthLastChild.cpp

@@ -1,72 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "StyleSheetNodeSelectorNthLastChild.h"
-#include "../../Include/RmlUi/Core/ElementText.h"
-
-namespace Rml {
-
-StyleSheetNodeSelectorNthLastChild::StyleSheetNodeSelectorNthLastChild()
-{
-}
-
-StyleSheetNodeSelectorNthLastChild::~StyleSheetNodeSelectorNthLastChild()
-{
-}
-
-// Returns true if the element's reverse index is (n * a) + b for a given integer value of n.
-bool StyleSheetNodeSelectorNthLastChild::IsApplicable(const Element* element, int a, int b)
-{
-	Element* parent = element->GetParentNode();
-	if (parent == nullptr)
-		return false;
-
-	// Start counting elements until we find this one.
-	int element_index = 1;
-	for (int i = parent->GetNumChildren() - 1; i >= 0; --i)
-	{
-		Element* child = parent->GetChild(i);
-
-		// Skip text nodes.
-		if (rmlui_dynamic_cast< ElementText* >(child) != nullptr)
-			continue;
-
-		// If we've found our element, then break; the current index is our element's index.
-		if (child == element)
-			break;
-
-		if (child->GetDisplay() == Style::Display::None)
-			continue;
-
-		element_index++;
-	}
-
-	return IsNth(a, b, element_index);
-}
-
-} // namespace Rml

+ 0 - 53
Source/Core/StyleSheetNodeSelectorNthLastChild.h

@@ -1,53 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_STYLESHEETNODESELECTORNTHLASTCHILD_H
-#define RMLUI_CORE_STYLESHEETNODESELECTORNTHLASTCHILD_H
-
-#include "StyleSheetNodeSelector.h"
-
-namespace Rml {
-
-/**
-	A node selector for the nth-last generic child.
-
-	@author Peter Curry
- */
-
-class StyleSheetNodeSelectorNthLastChild : public StyleSheetNodeSelector
-{
-public:
-	StyleSheetNodeSelectorNthLastChild();
-	virtual ~StyleSheetNodeSelectorNthLastChild();
-
-	// Returns true if the element's reverse index is (n * a) + b for a given integer value of n.
-	bool IsApplicable(const Element* element, int a, int b) override;
-};
-
-} // namespace Rml
-#endif

+ 0 - 70
Source/Core/StyleSheetNodeSelectorNthLastOfType.cpp

@@ -1,70 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "StyleSheetNodeSelectorNthLastOfType.h"
-#include "../../Include/RmlUi/Core/ElementText.h"
-
-namespace Rml {
-
-StyleSheetNodeSelectorNthLastOfType::StyleSheetNodeSelectorNthLastOfType()
-{
-}
-
-StyleSheetNodeSelectorNthLastOfType::~StyleSheetNodeSelectorNthLastOfType()
-{
-}
-
-// Returns true if the element index is (n * a) + b for a given integer value of n.
-bool StyleSheetNodeSelectorNthLastOfType::IsApplicable(const Element* element, int a, int b)
-{
-	Element* parent = element->GetParentNode();
-	if (parent == nullptr)
-		return false;
-
-	// Start counting elements until we find this one.
-	int element_index = 1;
-	for (int i = parent->GetNumChildren() - 1; i >= 0; --i)
-	{
-		Element* child = parent->GetChild(i);
-
-		// If we've found our element, then break; the current index is our element's index.
-		if (child == element)
-			break;
-
-		// Skip nodes that don't share our tag.
-		if (child->GetTagName() != element->GetTagName() ||
-			child->GetDisplay() == Style::Display::None)
-			continue;
-
-		element_index++;
-	}
-
-	return IsNth(a, b, element_index);
-}
-
-} // namespace Rml

+ 0 - 53
Source/Core/StyleSheetNodeSelectorNthLastOfType.h

@@ -1,53 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_STYLESHEETNODESELECTORNTHLASTOFTYPE_H
-#define RMLUI_CORE_STYLESHEETNODESELECTORNTHLASTOFTYPE_H
-
-#include "StyleSheetNodeSelector.h"
-
-namespace Rml {
-
-/**
-	A node selector for the nth-last generic child of its type.
-
-	@author Peter Curry
- */
-
-class StyleSheetNodeSelectorNthLastOfType : public StyleSheetNodeSelector
-{
-public:
-	StyleSheetNodeSelectorNthLastOfType();
-	virtual ~StyleSheetNodeSelectorNthLastOfType();
-
-	// Returns true if the element index is (n * a) + b for a given integer value of n.
-	bool IsApplicable(const Element* element, int a, int b) override;
-};
-
-} // namespace Rml
-#endif

+ 0 - 70
Source/Core/StyleSheetNodeSelectorNthOfType.cpp

@@ -1,70 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "StyleSheetNodeSelectorNthOfType.h"
-#include "../../Include/RmlUi/Core/ElementText.h"
-
-namespace Rml {
-
-StyleSheetNodeSelectorNthOfType::StyleSheetNodeSelectorNthOfType()
-{
-}
-
-StyleSheetNodeSelectorNthOfType::~StyleSheetNodeSelectorNthOfType()
-{
-}
-
-// Returns true if the element index is (n * a) + b for a given integer value of n.
-bool StyleSheetNodeSelectorNthOfType::IsApplicable(const Element* element, int a, int b)
-{
-	Element* parent = element->GetParentNode();
-	if (parent == nullptr)
-		return false;
-
-	// Start counting elements until we find this one.
-	int element_index = 1;
-	for (int i = 0; i < parent->GetNumChildren(); ++i)
-	{
-		Element* child = parent->GetChild(i);
-
-		// If we've found our element, then break; the current index is our element's index.
-		if (child == element)
-			break;
-
-		// Skip nodes that don't share our tag.
-		if (child->GetTagName() != element->GetTagName() ||
-			child->GetDisplay() == Style::Display::None)
-			continue;
-
-		element_index++;
-	}
-
-	return IsNth(a, b, element_index);
-}
-
-} // namespace Rml

+ 0 - 53
Source/Core/StyleSheetNodeSelectorNthOfType.h

@@ -1,53 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_STYLESHEETNODESELECTORNTHOFTYPE_H
-#define RMLUI_CORE_STYLESHEETNODESELECTORNTHOFTYPE_H
-
-#include "StyleSheetNodeSelector.h"
-
-namespace Rml {
-
-/**
-	A node selector for the nth generic child.
-
-	@author Peter Curry
- */
-
-class StyleSheetNodeSelectorNthOfType : public StyleSheetNodeSelector
-{
-public:
-	StyleSheetNodeSelectorNthOfType();
-	virtual ~StyleSheetNodeSelectorNthOfType();
-
-	// Returns true if the element index is (n * a) + b for a given integer value of n.
-	bool IsApplicable(const Element* element, int a, int b) override;
-};
-
-} // namespace Rml
-#endif

+ 0 - 71
Source/Core/StyleSheetNodeSelectorOnlyChild.cpp

@@ -1,71 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "StyleSheetNodeSelectorOnlyChild.h"
-#include "../../Include/RmlUi/Core/ElementText.h"
-
-namespace Rml {
-
-StyleSheetNodeSelectorOnlyChild::StyleSheetNodeSelectorOnlyChild()
-{
-}
-
-StyleSheetNodeSelectorOnlyChild::~StyleSheetNodeSelectorOnlyChild()
-{
-}
-
-// Returns true if the element is the only non-trivial DOM child of its parent.
-bool StyleSheetNodeSelectorOnlyChild::IsApplicable(const Element* element, int RMLUI_UNUSED_PARAMETER(a), int RMLUI_UNUSED_PARAMETER(b))
-{
-	RMLUI_UNUSED(a);
-	RMLUI_UNUSED(b);
-
-	Element* parent = element->GetParentNode();
-	if (parent == nullptr)
-		return false;
-
-	for (int i = 0; i < parent->GetNumChildren(); ++i)
-	{
-		Element* child = parent->GetChild(i);
-
-		// Skip the child if it is our element.
-		if (child == element)
-			continue;
-
-		// Skip the child if it is trivial.
-		if (rmlui_dynamic_cast< const ElementText* >(element) != nullptr ||
-			child->GetDisplay() == Style::Display::None)
-			continue;
-
-		return false;
-	}
-
-	return true;
-}
-
-} // namespace Rml

+ 0 - 53
Source/Core/StyleSheetNodeSelectorOnlyChild.h

@@ -1,53 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_STYLESHEETNODESELECTORONLYCHILD_H
-#define RMLUI_CORE_STYLESHEETNODESELECTORONLYCHILD_H
-
-#include "StyleSheetNodeSelector.h"
-
-namespace Rml {
-
-/**
-	A node selector for an only child.
-
-	@author Peter Curry
- */
-
-class StyleSheetNodeSelectorOnlyChild : public StyleSheetNodeSelector
-{
-public:
-	StyleSheetNodeSelectorOnlyChild();
-	virtual ~StyleSheetNodeSelectorOnlyChild();
-
-	// Returns true if the element is the only non-trivial DOM child of its parent.
-	bool IsApplicable(const Element* element, int a, int b) override;
-};
-
-} // namespace Rml
-#endif

+ 0 - 72
Source/Core/StyleSheetNodeSelectorOnlyOfType.cpp

@@ -1,72 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#include "StyleSheetNodeSelectorOnlyOfType.h"
-#include "../../Include/RmlUi/Core/ElementText.h"
-
-namespace Rml {
-
-StyleSheetNodeSelectorOnlyOfType::StyleSheetNodeSelectorOnlyOfType()
-{
-}
-
-StyleSheetNodeSelectorOnlyOfType::~StyleSheetNodeSelectorOnlyOfType()
-{
-}
-
-// Returns true if the element is the only DOM child of its parent of its type.
-bool StyleSheetNodeSelectorOnlyOfType::IsApplicable(const Element* element, int RMLUI_UNUSED_PARAMETER(a), int RMLUI_UNUSED_PARAMETER(b))
-{
-	RMLUI_UNUSED(a);
-	RMLUI_UNUSED(b);
-
-	Element* parent = element->GetParentNode();
-	if (parent == nullptr)
-		return false;
-
-	for (int i = 0; i < parent->GetNumChildren(); ++i)
-	{
-		Element* child = parent->GetChild(i);
-
-		// Skip the child if it is our element.
-		if (child == element)
-			continue;
-
-		// Skip the child if it does not share our tag.
-		if (child->GetTagName() != element->GetTagName() ||
-			child->GetDisplay() == Style::Display::None)
-			continue;
-
-		// We've found a similarly-tagged child to our element; selector fails.
-		return false;
-	}
-
-	return true;
-}
-
-} // namespace Rml

+ 0 - 53
Source/Core/StyleSheetNodeSelectorOnlyOfType.h

@@ -1,53 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_STYLESHEETNODESELECTORONLYOFTYPE_H
-#define RMLUI_CORE_STYLESHEETNODESELECTORONLYOFTYPE_H
-
-#include "StyleSheetNodeSelector.h"
-
-namespace Rml {
-
-/**
-	A node selector for the only child of its type.
-
-	@author Peter Curry
- */
-
-class StyleSheetNodeSelectorOnlyOfType : public StyleSheetNodeSelector
-{
-public:
-	StyleSheetNodeSelectorOnlyOfType();
-	virtual ~StyleSheetNodeSelectorOnlyOfType();
-
-	// Returns true if the element is the only DOM child of its parent of its type.
-	bool IsApplicable(const Element* element, int a, int b) override;
-};
-
-} // namespace Rml
-#endif

+ 62 - 36
Source/Core/StyleSheetParser.cpp

@@ -558,7 +558,7 @@ bool StyleSheetParser::Parse(MediaBlockList& style_sheets, Stream* _stream, int
 						continue;
 
 					StringList rule_name_list;
-					StringUtilities::ExpandString(rule_name_list, pre_token_str);
+					StringUtilities::ExpandString(rule_name_list, pre_token_str, ',', '(', ')');
 
 					// Add style nodes to the root of the tree
 					for (size_t i = 0; i < rule_name_list.size(); i++)
@@ -754,7 +754,7 @@ StyleSheetNodeListRaw StyleSheetParser::ConstructNodes(StyleSheetNode& root_node
 	const PropertyDictionary empty_properties;
 
 	StringList selector_list;
-	StringUtilities::ExpandString(selector_list, selectors);
+	StringUtilities::ExpandString(selector_list, selectors, ',', '(', ')');
 
 	StyleSheetNodeListRaw leaf_nodes;
 
@@ -876,20 +876,26 @@ StyleSheetNode* StyleSheetParser::ImportProperties(StyleSheetNode* node, String
 
 	StringList nodes;
 
-	// Find child combinators, the RCSS '>' rule.
-	size_t i_child = rule_name.find('>');
-	while (i_child != String::npos)
+	// Combinator rules can be formatted several ways by users, here we ensure consistent formatting before we can continue with the parsing below.
+	// E.g. converts combinations such as "A > B", "A >B", "A>B"  to  "A> B".
+	for (const char combinator : {'>', '+', '~'})
 	{
-		// So we found one! Next, we want to format the rule such that the '>' is located at the 
-		// end of the left-hand-side node, and that there is a space to the right-hand-side. This ensures that
-		// the selector is applied to the "parent", and that parent and child are expanded properly below.
-		size_t i_begin = i_child;
-		while (i_begin > 0 && rule_name[i_begin - 1] == ' ')
-			i_begin--;
-
-		const size_t i_end = i_child + 1;
-		rule_name.replace(i_begin, i_end - i_begin, "> ");
-		i_child = rule_name.find('>', i_begin + 1);
+		// Find all combinators of the given type.
+		size_t i_child = rule_name.find(combinator);
+		while (i_child != String::npos)
+		{
+			// So we found one! Next, we want to format the rule such that e.g. the '>' is located at the
+			// end of the left-hand-side node, and that there is a space to the right-hand-side. This ensures that
+			// the selector is applied to the "parent", and that parent and child are expanded properly below.
+			size_t i_begin = i_child;
+			while (i_begin > 0 && rule_name[i_begin - 1] == ' ')
+				i_begin--;
+
+			const size_t i_end = i_child + 1;
+			const char replacement_str[] = {combinator, ' ', '\0'};
+			rule_name.replace(i_begin, i_end - i_begin, (const char*)replacement_str);
+			i_child = rule_name.find(combinator, i_begin + 1);
+		}
 	}
 
 	// Expand each individual node separated by spaces. Don't expand inside parenthesis because of structural selectors.
@@ -905,42 +911,62 @@ StyleSheetNode* StyleSheetParser::ImportProperties(StyleSheetNode* node, String
 		StringList classes;
 		StringList pseudo_classes;
 		StructuralSelectorList structural_pseudo_classes;
-		bool child_combinator = false;
+		SelectorCombinator combinator = SelectorCombinator::None;
 
 		size_t index = 0;
 		while (index < name.size())
 		{
 			size_t start_index = index;
 			size_t end_index = index + 1;
+			int parenthesis_count = 0;
 
 			// Read until we hit the next identifier.
-			while (end_index < name.size() &&
-				   name[end_index] != '#' &&
-				   name[end_index] != '.' &&
-				   name[end_index] != ':' &&
-				   name[end_index] != '>')
-				end_index++;
+			for (; end_index < name.size(); end_index++)
+			{
+				static const String identifiers = "#.:>+~";
+				if (parenthesis_count == 0 && identifiers.find(name[end_index]) != String::npos)
+					break;
+
+				if (name[end_index] == '(')
+					parenthesis_count += 1;
+				else if (name[end_index] == ')')
+					parenthesis_count -= 1;
+			}
 
 			String identifier = name.substr(start_index, end_index - start_index);
 			if (!identifier.empty())
 			{
 				switch (identifier[0])
 				{
-					case '#':	id = identifier.substr(1); break;
-					case '.':	classes.push_back(identifier.substr(1)); break;
-					case ':':
-					{
-						String pseudo_class_name = identifier.substr(1);
-						StructuralSelector node_selector = StyleSheetFactory::GetSelector(pseudo_class_name);
-						if (node_selector.selector)
-							structural_pseudo_classes.push_back(node_selector);
-						else
-							pseudo_classes.push_back(pseudo_class_name);
-					}
+				case '#':
+					id = identifier.substr(1);
+					break;
+				case '.':
+					classes.push_back(identifier.substr(1));
+					break;
+				case ':':
+				{
+					String pseudo_class_name = identifier.substr(1);
+					StructuralSelector node_selector = StyleSheetFactory::GetSelector(pseudo_class_name);
+					if (node_selector.type != StructuralSelectorType::Invalid)
+						structural_pseudo_classes.push_back(node_selector);
+					else
+						pseudo_classes.push_back(pseudo_class_name);
+				}
+				break;
+				case '>':
+					combinator = SelectorCombinator::Child;
+					break;
+				case '+':
+					combinator = SelectorCombinator::NextSibling;
+					break;
+				case '~':
+					combinator = SelectorCombinator::SubsequentSibling;
 					break;
-					case '>':	child_combinator = true; break;
 
-					default:	if(identifier != "*") tag = identifier;
+				default:
+					if (identifier != "*")
+						tag = identifier;
 				}
 			}
 
@@ -953,7 +979,7 @@ StyleSheetNode* StyleSheetParser::ImportProperties(StyleSheetNode* node, String
 		std::sort(structural_pseudo_classes.begin(), structural_pseudo_classes.end());
 
 		// Get the named child node.
-		leaf_node = leaf_node->GetOrCreateChildNode(std::move(tag), std::move(id), std::move(classes), std::move(pseudo_classes), std::move(structural_pseudo_classes), child_combinator);
+		leaf_node = leaf_node->GetOrCreateChildNode(std::move(tag), std::move(id), std::move(classes), std::move(pseudo_classes), std::move(structural_pseudo_classes), combinator);
 	}
 
 	// Merge the new properties with those already on the leaf node.

+ 348 - 0
Source/Core/StyleSheetSelector.cpp

@@ -0,0 +1,348 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "StyleSheetSelector.h"
+#include "../../Include/RmlUi/Core/Element.h"
+#include "StyleSheetNode.h"
+
+namespace Rml {
+
+static inline bool IsTextElement(const Element* element)
+{
+	return element->GetTagName() == "#text";
+}
+
+// Returns true if a positive integer can be found for n in the equation an + b = count.
+static bool IsNth(int a, int b, int count)
+{
+	int x = count;
+	x -= b;
+	if (a != 0)
+		x /= a;
+
+	return (x >= 0 && x * a + b == count);
+}
+
+bool IsSelectorApplicable(const Element* element, const StructuralSelector& selector)
+{
+	RMLUI_ASSERT(element);
+
+	switch (selector.type)
+	{
+	case StructuralSelectorType::Nth_Child:
+	{
+		Element* parent = element->GetParentNode();
+		if (!parent)
+			return false;
+
+		// Start counting elements until we find this one.
+		int element_index = 1;
+		for (int i = 0; i < parent->GetNumChildren(); i++)
+		{
+			Element* child = parent->GetChild(i);
+
+			// Skip text nodes.
+			if (IsTextElement(child))
+				continue;
+
+			// If we've found our element, then break; the current index is our element's index.
+			if (child == element)
+				break;
+
+			element_index++;
+		}
+
+		return IsNth(selector.a, selector.b, element_index);
+	}
+	break;
+	case StructuralSelectorType::Nth_Last_Child:
+	{
+		Element* parent = element->GetParentNode();
+		if (!parent)
+			return false;
+
+		// Start counting elements until we find this one.
+		int element_index = 1;
+		for (int i = parent->GetNumChildren() - 1; i >= 0; --i)
+		{
+			Element* child = parent->GetChild(i);
+
+			// Skip text nodes.
+			if (IsTextElement(child))
+				continue;
+
+			// If we've found our element, then break; the current index is our element's index.
+			if (child == element)
+				break;
+
+			element_index++;
+		}
+
+		return IsNth(selector.a, selector.b, element_index);
+	}
+	break;
+	case StructuralSelectorType::Nth_Of_Type:
+	{
+		Element* parent = element->GetParentNode();
+		if (!parent)
+			return false;
+
+		// Start counting elements until we find this one.
+		int element_index = 1;
+		const int num_children = parent->GetNumChildren();
+		for (int i = 0; i < num_children; i++)
+		{
+			Element* child = parent->GetChild(i);
+
+			// If we've found our element, then break; the current index is our element's index.
+			if (child == element)
+				break;
+
+			// Skip nodes that don't share our tag.
+			if (child->GetTagName() != element->GetTagName())
+				continue;
+
+			element_index++;
+		}
+
+		return IsNth(selector.a, selector.b, element_index);
+	}
+	break;
+	case StructuralSelectorType::Nth_Last_Of_Type:
+	{
+		Element* parent = element->GetParentNode();
+		if (!parent)
+			return false;
+
+		// Start counting elements until we find this one.
+		int element_index = 1;
+		for (int i = parent->GetNumChildren() - 1; i >= 0; --i)
+		{
+			Element* child = parent->GetChild(i);
+
+			// If we've found our element, then break; the current index is our element's index.
+			if (child == element)
+				break;
+
+			// Skip nodes that don't share our tag.
+			if (child->GetTagName() != element->GetTagName())
+				continue;
+
+			element_index++;
+		}
+
+		return IsNth(selector.a, selector.b, element_index);
+	}
+	break;
+	case StructuralSelectorType::First_Child:
+	{
+		Element* parent = element->GetParentNode();
+		if (!parent)
+			return false;
+
+		int child_index = 0;
+		while (child_index < parent->GetNumChildren())
+		{
+			// If this child (the first non-text child) is our element, then the selector succeeds.
+			Element* child = parent->GetChild(child_index);
+			if (child == element)
+				return true;
+
+			// If this child is not a text element, then the selector fails; this element is non-trivial.
+			if (!IsTextElement(child))
+				return false;
+
+			// Otherwise, skip over the text element to find the last non-trivial element.
+			child_index++;
+		}
+
+		return false;
+	}
+	break;
+	case StructuralSelectorType::Last_Child:
+	{
+		Element* parent = element->GetParentNode();
+		if (!parent)
+			return false;
+
+		int child_index = parent->GetNumChildren() - 1;
+		while (child_index >= 0)
+		{
+			// If this child (the last non-text child) is our element, then the selector succeeds.
+			Element* child = parent->GetChild(child_index);
+			if (child == element)
+				return true;
+
+			// If this child is not a text element, then the selector fails; this element is non-trivial.
+			if (!IsTextElement(child))
+				return false;
+
+			// Otherwise, skip over the text element to find the last non-trivial element.
+			child_index--;
+		}
+
+		return false;
+	}
+	break;
+	case StructuralSelectorType::First_Of_Type:
+	{
+		Element* parent = element->GetParentNode();
+		if (!parent)
+			return false;
+
+		int child_index = 0;
+		while (child_index < parent->GetNumChildren())
+		{
+			// If this child is our element, then it's the first one we've found with our tag; the selector succeeds.
+			Element* child = parent->GetChild(child_index);
+			if (child == element)
+				return true;
+
+			// Otherwise, if this child shares our element's tag, then our element is not the first tagged child; the selector fails.
+			if (child->GetTagName() == element->GetTagName())
+				return false;
+
+			child_index++;
+		}
+
+		return false;
+	}
+	break;
+	case StructuralSelectorType::Last_Of_Type:
+	{
+		Element* parent = element->GetParentNode();
+		if (!parent)
+			return false;
+
+		int child_index = parent->GetNumChildren() - 1;
+		while (child_index >= 0)
+		{
+			// If this child is our element, then it's the first one we've found with our tag; the selector succeeds.
+			Element* child = parent->GetChild(child_index);
+			if (child == element)
+				return true;
+
+			// Otherwise, if this child shares our element's tag, then our element is not the first tagged child; the selector fails.
+			if (child->GetTagName() == element->GetTagName())
+				return false;
+
+			child_index--;
+		}
+
+		return false;
+	}
+	break;
+	case StructuralSelectorType::Only_Child:
+	{
+		Element* parent = element->GetParentNode();
+		if (!parent)
+			return false;
+
+		const int num_children = parent->GetNumChildren();
+		for (int i = 0; i < num_children; i++)
+		{
+			Element* child = parent->GetChild(i);
+
+			// Skip the child if it is our element.
+			if (child == element)
+				continue;
+
+			// Skip the child if it is trivial.
+			if (IsTextElement(child))
+				continue;
+
+			return false;
+		}
+
+		return true;
+	}
+	break;
+	case StructuralSelectorType::Only_Of_Type:
+	{
+		Element* parent = element->GetParentNode();
+		if (!parent)
+			return false;
+
+		const int num_children = parent->GetNumChildren();
+		for (int i = 0; i < num_children; i++)
+		{
+			Element* child = parent->GetChild(i);
+
+			// Skip the child if it is our element.
+			if (child == element)
+				continue;
+
+			// Skip the child if it does not share our tag.
+			if (child->GetTagName() != element->GetTagName())
+				continue;
+
+			// We've found a similarly-tagged child to our element; selector fails.
+			return false;
+		}
+
+		return true;
+	}
+	break;
+	case StructuralSelectorType::Empty:
+	{
+		return element->GetNumChildren() == 0;
+	}
+	break;
+	case StructuralSelectorType::Not:
+	{
+		if (!selector.selector_tree)
+		{
+			RMLUI_ERROR;
+			return false;
+		}
+
+		bool inner_selector_matches = false;
+
+		for (const StyleSheetNode* node : selector.selector_tree->leafs)
+		{
+			if (node->IsApplicable(element))
+			{
+				inner_selector_matches = true;
+				break;
+			}
+		}
+
+		return !inner_selector_matches;
+	}
+	break;
+	case StructuralSelectorType::Invalid:
+	{
+		RMLUI_ERROR;
+	}
+	break;
+	}
+
+	return false;
+}
+
+} // namespace Rml

+ 118 - 0
Source/Core/StyleSheetSelector.h

@@ -0,0 +1,118 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RMLUI_CORE_STYLESHEETSELECTOR_H
+#define RMLUI_CORE_STYLESHEETSELECTOR_H
+
+#include "../../Include/RmlUi/Core/Types.h"
+#include <tuple>
+
+namespace Rml {
+
+class Element;
+class StyleSheetNode;
+struct SelectorTree;
+
+namespace SelectorSpecificity {
+	enum {
+		// Constants used to determine the specificity of a selector.
+		Tag = 10'000,
+		Class = 100'000,
+		PseudoClass = Class,
+		ID = 1'000'000,
+	};
+}
+
+enum class StructuralSelectorType {
+	Invalid,
+	Nth_Child,
+	Nth_Last_Child,
+	Nth_Of_Type,
+	Nth_Last_Of_Type,
+	First_Child,
+	Last_Child,
+	First_Of_Type,
+	Last_Of_Type,
+	Only_Child,
+	Only_Of_Type,
+	Empty,
+	Not
+};
+
+struct StructuralSelector {
+	StructuralSelector(StructuralSelectorType type, int a, int b) : type(type), a(a), b(b) {}
+	StructuralSelector(StructuralSelectorType type, SharedPtr<const SelectorTree> tree, int specificity) :
+		type(type), specificity(specificity), selector_tree(std::move(tree))
+	{}
+
+	StructuralSelectorType type = StructuralSelectorType::Invalid;
+
+	// For counting selectors, the following are the 'a' and 'b' variables of an + b.
+	int a = 0;
+	int b = 0;
+
+	// Specificity is usually determined like a pseudo class, but some types override this value.
+	int specificity = SelectorSpecificity::PseudoClass;
+
+	// For selectors that contain internal selectors such as :not().
+	SharedPtr<const SelectorTree> selector_tree;
+};
+
+inline bool operator==(const StructuralSelector& a, const StructuralSelector& b)
+{
+	// Currently sub-selectors (selector_tree) are only superficially compared. This mainly has the consequence that selectors with a sub-selector
+	// which are instantiated separately will never compare equal, even if they have the exact same sub-selector expression. This further results in
+	// such selectors not being de-duplicated. This should not lead to any functional differences but leads to potentially missed memory/performance
+	// optimizations. E.g. 'div a, div b' will combine the two div nodes, while ':not(div) a, :not(div) b' will not combine the two not-div nodes.
+	return a.type == b.type && a.a == b.a && a.b == b.b && a.selector_tree == b.selector_tree;
+}
+inline bool operator<(const StructuralSelector& a, const StructuralSelector& b)
+{
+	return std::tie(a.type, a.a, a.b, a.selector_tree) < std::tie(b.type, b.a, b.b, b.selector_tree);
+}
+
+// A tree of unstyled style sheet nodes.
+struct SelectorTree {
+	UniquePtr<StyleSheetNode> root;
+	Vector<StyleSheetNode*> leafs; // Owned by root.
+};
+
+enum class SelectorCombinator : byte {
+	None,
+	Child,             // The 'E > F' combinator: Matches if F is a child of E.
+	NextSibling,       // The 'E + F' combinator: Matches if F is immediately preceded by E.
+	SubsequentSibling, // The 'E ~ F' combinator: Matches if F is preceded by E.
+};
+
+/// Returns true if the the node the given selector is discriminating for is applicable to a given element.
+/// @param element[in] The element to determine node applicability for.
+/// @param selector[in] The selector to test against the element.
+bool IsSelectorApplicable(const Element* element, const StructuralSelector& selector);
+
+} // namespace Rml
+#endif

+ 58 - 23
Tests/Source/Benchmarks/ElementStyle.cpp → Tests/Source/Benchmarks/Selectors.cpp

@@ -92,33 +92,47 @@ static int GetNumDescendentElements(Element* element)
 
 static constexpr int num_rule_iterations = 10;
 
-static String GenerateRCSS(bool with_tag, bool with_id, bool with_class, bool with_pseudo_class, bool with_child_div, String& out_rule_name)
+enum SelectorFlags {
+	NO_SELECTOR,
+	TAG = 1 << 0,
+	ID = 1 << 1,
+	CLASS = 1 << 2,
+	PSEUDO_CLASS = 1 << 3,
+	NUM_COMBINATIONS = 1 << 4,
+};
+
+static String GenerateRCSS(SelectorFlags selectors, const String& complex_selector, String& out_rule_name)
 {
 	static_assert('a' < 'z' && 'a' + 25 == 'z', "Assumes ASCII characters");
 
 	auto GenerateRule = [=](const String& name) {
 		String rule;
-		if (!with_tag && !with_id && !with_class && !with_pseudo_class)
+		if (!complex_selector.empty())
+		{
+			rule = complex_selector;
+		}
+		else if (selectors == NO_SELECTOR || name.empty())
+		{
 			rule += '*';
+		}
 		else
 		{
-			if (with_tag)
+			if (selectors & TAG)
 			{
-				if (with_id || with_class || with_pseudo_class)
-					rule += "div";
-				else
+				if (selectors == TAG)
 					rule += name;
+				else
+					rule += "div";
 			}
 
-			if (with_id)
+			if (selectors & ID)
 				rule += '#' + name;
-			if (with_class)
+			if (selectors & CLASS)
 				rule += '.' + name;
-			if (with_pseudo_class)
+			if (selectors & PSEUDO_CLASS)
 				rule += ':' + name;
 		}
-		if (with_child_div)
-			rule += " div";
+
 		return rule;
 	};
 
@@ -135,6 +149,15 @@ static String GenerateRCSS(bool with_tag, bool with_id, bool with_class, bool wi
 
 			// Set a property that does not require a layout change
 			result += CreateString(64, " { scrollbar-margin: %dpx; }\n", int(c - 'a') + 1);
+
+			
+#if 1
+			// This conditions ensures that only a single version of the complex selector is included. This can be disabled to test how well the rules
+			// are de-duplicated, since then a lot more selectors will be tested per update call. Rules that contain sub-selectors are currently not
+			// de-duplicated, such as :not().
+			if (!complex_selector.empty())
+				return result;
+#endif
 		}
 	}
 
@@ -178,7 +201,7 @@ static String GenerateRml(const int num_rows)
 	return rml;
 }
 
-TEST_CASE("elementstyle")
+TEST_CASE("Selectors")
 {
 	Context* context = TestsShell::GetContext();
 	REQUIRE(context);
@@ -193,23 +216,35 @@ TEST_CASE("elementstyle")
 	// "dummy" style rules added to the style sheet.
 
 	nanobench::Bench bench;
-	bench.title("ElementStyle (rule name)");
+	bench.title("Selector (rule name)");
 	bench.timeUnit(std::chrono::microseconds(1), "us");
 	bench.relative(true);
 
-	for (int i = 0; i <= 0b11111 + 1; i++)
+	const Vector<String> complex_selectors = {
+		"div",
+		"div div",
+		"div > div",
+		"div + div",
+		"div ~ div",
+		":empty div",
+		":only-child div",
+		":first-child div",
+		":nth-child(2n+3) div",
+		":nth-of-type(2n+3) div",
+		":not(div) div",
+	};
+
+	for (int i = 0; i < NUM_COMBINATIONS + (int)complex_selectors.size(); i++)
 	{
 		const bool reference = (i == 0);
-		const int flags = i - 1;
+		const SelectorFlags selector_flags = SelectorFlags(i < NUM_COMBINATIONS ? i : NO_SELECTOR);
+		const String& complex_selector = (i < NUM_COMBINATIONS ? String() : complex_selectors[i - NUM_COMBINATIONS]);
 
-		const bool with_tag = flags & (1 << 0);
-		const bool with_id = flags & (1 << 1);
-		const bool with_class = flags & (1 << 2);
-		const bool with_pseudo_class = flags & (1 << 3);
-		const bool with_child_div = flags & (1 << 4);
-
-		String name = "Reference (no style rules)";
-		const String styles = reference ? "" : GenerateRCSS(with_tag, with_id, with_class, with_pseudo_class, with_child_div, name);
+		String name, styles;
+		if (reference)
+			name = "Reference (no style rules)";
+		else
+			styles = GenerateRCSS(selector_flags, complex_selector, name);
 
 		const String compiled_document_rml = Rml::CreateString(1000 + styles.size(), document_rml_template, styles.c_str());
 

+ 125 - 30
Tests/Source/UnitTests/Selectors.cpp

@@ -26,7 +26,7 @@
  *
  */
 
-#include "../Common/TestsInterface.h"
+#include "../Common/TestsShell.h"
 #include <RmlUi/Core/Context.h>
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/Element.h>
@@ -46,6 +46,7 @@ static const String doc_begin = R"(
 			width: 400px;
 			height: 300px;
 			margin: auto;
+			font-family: LatoLatin;
 		}
 )";
 static const String doc_end = R"(
@@ -57,9 +58,10 @@ static const String doc_end = R"(
 	<div  id="Z" class="hello world"/>
 	<div  id="P" class="parent">
 		<h1 id="A"/>
+		Some text
 		<p  id="B"/>
 		<p  id="C"/>
-		<p  id="D"> <span id="D0"/><span id="D1"/> </p>
+		<p  id="D"> <span id="D0">  </span><span id="D1">Text</span> </p>
 		<h3 id="E"/>
 		<p  id="F"> <span id="F0"/> </p>
 		<p  id="G"/>
@@ -70,10 +72,24 @@ static const String doc_end = R"(
 </rml>
 )";
 
+enum class SelectorOp { None, RemoveElementsByIds, InsertElementBefore, RemoveClasses, RemoveChecked };
+
 struct QuerySelector {
+	QuerySelector(String selector, String expected_ids) : selector(std::move(selector)), expected_ids(std::move(expected_ids)) {}
+	QuerySelector(String selector, String expected_ids, SelectorOp operation, String operation_argument, String expected_ids_after_operation) :
+		selector(std::move(selector)), expected_ids(std::move(expected_ids)), operation(operation), operation_argument(std::move(operation_argument)),
+		expected_ids_after_operation(std::move(expected_ids_after_operation))
+	{}
 	String selector;
 	String expected_ids;
+
+	// Optionally also test the selector after dynamically making a structural operation on the document.
+	SelectorOp operation = SelectorOp::None;
+	String operation_argument;
+	String expected_ids_after_operation;
 };
+
+// clang-format off
 static const Vector<QuerySelector> query_selectors =
 {
 	{ "span",                        "Y D0 D1 F0" },
@@ -85,26 +101,54 @@ static const Vector<QuerySelector> query_selectors =
 	{ "body > .hello",               "X Z" },
 	{ ".parent *",                   "A B C D D0 D1 E F F0 G H" },
 	{ ".parent > *",                 "A B C D E F G H" },
-	{ ":checked",                    "I" },
+	{ ":checked",                    "I",               SelectorOp::RemoveChecked,        "I", "" },
 	{ ".parent :nth-child(odd)",     "A C D0 E F0 G" },
-	{ ".parent > :nth-child(even)",  "B D F H" },
-	{ ":first-child",                "X A D0 F0" },
-	{ ":last-child",                 "D1 F0 H I" },
-	{ "p:nth-child(2)",              "B" },
-	{ "h1:nth-child(2)",             "" },
-	{ "p:nth-child(3n+1)",           "D G" },
+	{ ".parent > :nth-child(even)",  "B D F H",         SelectorOp::RemoveClasses,        "parent", "" },
+	{ ":first-child",                "X A D0 F0",       SelectorOp::RemoveElementsByIds,  "A F0", "X B D0" },
+	{ ":last-child",                 "D1 F0 H I",       SelectorOp::RemoveElementsByIds,  "D0 H", "D1 F0 G I" },
+	{ "h1:nth-child(2)",             "",                SelectorOp::InsertElementBefore,  "A",    "A" },
+	{ "p:nth-child(2)",              "B",               SelectorOp::InsertElementBefore,  "A",    "" },
+	{ "p:nth-child(2)",              "B",               SelectorOp::RemoveElementsByIds,  "A",    "C" },
+	{ "p:nth-child(3n+1)",           "D G",             SelectorOp::RemoveElementsByIds,  "B",    "H" },
 	{ "p:nth-child(3n + 1)",         "D G" },
 	{ "#P > :nth-last-child(2n+1)",  "B D F H" },
 	{ "#P p:nth-of-type(odd)",       "B D G" },
+	{ "span:first-child",            "D0 F0" },
+	{ "body span:first-child",       "D0 F0" },
+	{ "body > p span:first-child",   "" },
+	{ "body > div span:first-child", "D0 F0" },
+	{ ":nth-child(4) * span:first-child", "D0 F0",      SelectorOp::RemoveElementsByIds,  "X",    "" },
 	{ "p:nth-last-of-type(3n+1)",    "D H" },
 	{ ":first-of-type",              "X Y A B D0 E F0 I" },
 	{ ":last-of-type",               "Y P A D1 E F0 H I" },
-	{ ":only-child",                 "F0" },
+	{ ":only-child",                 "F0",              SelectorOp::RemoveElementsByIds,  "D0",    "D1 F0" },
 	{ ":only-of-type",               "Y A E F0 I" },
-	{ "span:empty",                  "Y D0 D1 F0" },
-	{ ".hello.world, #P span, #I",   "Z D0 D1 F0 I" },
+	{ "span:empty",                  "Y D0 F0" },
+	{ ".hello.world, #P span, #I",   "Z D0 D1 F0 I",    SelectorOp::RemoveClasses,        "world", "D0 D1 F0 I" },
 	{ "body * span",                 "D0 D1 F0" },
+	{ "D1 *",                        "" },
+	{ "#E + #F",                     "F",               SelectorOp::InsertElementBefore,  "F",     "" },
+	{ "#E+#F",                       "F" },
+	{ "#E +#F",                      "F" },
+	{ "#E+ #F",                      "F" },
+	{ "#F + #E",                     "" },
+	{ "div.parent > #B + p",         "C" },
+	{ "div.parent > #B + div",       "" },
+	{ "#B ~ #F",                     "F" },
+	{ "#B~#F",                       "F" },
+	{ "#B ~#F",                      "F" },
+	{ "#B~ #F",                      "F" },
+	{ "#F ~ #B",                     "" },
+	{ "div.parent > #B ~ p:empty",   "C G H",           SelectorOp::InsertElementBefore,  "H",     "C G Inserted H" },
+	{ "div.parent > #B ~ * span",    "D0 D1 F0" },
+	{ ":not(*)",                     "" },
+	{ ":not(span)",                  "X Z P A B C D E F G H I" },
+	{ "#D :not(#D0)",                "D1" },
+	{ "body > :not(:checked)",       "X Y Z P",         SelectorOp::RemoveChecked,        "I", "X Y Z P I" },
+	{ "div.hello:not(.world)",       "X" },
+	{ ":not(div,:nth-child(2),p *)", "A C D E F G H I" },
 };
+
 struct ClosestSelector {
 	String start_id;
 	String selector;
@@ -123,7 +167,7 @@ static const Vector<ClosestSelector> closest_selectors =
 	{ "D1",  ":nth-child(4)",    "D" },
 	{ "D1",  "div:nth-child(4)", "P" },
 };
-
+// clang-format on
 
 // Recursively iterate through 'element' and all of its descendants to find all
 // elements matching a particular property used to tag matching selectors.
@@ -132,7 +176,9 @@ static void GetMatchingIds(String& matching_ids, Element* element)
 	String id = element->GetId();
 	if (!id.empty() && element->GetProperty<int>("drag") == (int)Style::Drag::Drag)
 	{
-		matching_ids += id + ' ';
+		if (!matching_ids.empty())
+			matching_ids += ' ';
+		matching_ids += id;
 	}
 
 	for (int i = 0; i < element->GetNumChildren(); i++)
@@ -157,20 +203,39 @@ static String ElementListToIds(const ElementList& elements)
 	return result;
 }
 
-TEST_CASE("Selectors")
+static void RemoveElementsWithIds(ElementDocument* document, const String& remove_ids)
 {
-	const Vector2i window_size(1024, 768);
-
-	TestsSystemInterface system_interface;
-	TestsRenderInterface render_interface;
-
-	SetRenderInterface(&render_interface);
-	SetSystemInterface(&system_interface);
-
-	Initialise();
+	StringList remove_id_list;
+	StringUtilities::ExpandString(remove_id_list, remove_ids, ' ');
+	for (const String& id : remove_id_list)
+	{
+		if (Element* element = document->GetElementById(id))
+			element->GetParentNode()->RemoveChild(element);
+	}
+}
+static void RemoveClassesFromAllElements(ElementDocument* document, const String& remove_classes)
+{
+	StringList class_list;
+	StringUtilities::ExpandString(class_list, remove_classes, ' ');
+	for (const String& name : class_list)
+	{
+		ElementList element_list;
+		document->GetElementsByClassName(element_list, name);
+		for (Element* element : element_list)
+			element->SetClass(name, false);
+	}
+}
+static void InsertElementBefore(ElementDocument* document, const String& before_id)
+{
+	Element* element = document->GetElementById(before_id);
+	ElementPtr new_element = document->CreateElement("p");
+	new_element->SetId("Inserted");
+	element->GetParentNode()->InsertBefore(std::move(new_element), element);
+}
 
-	Context* context = Rml::CreateContext("main", window_size);
-	REQUIRE(context);
+TEST_CASE("Selectors")
+{
+	Context* context = TestsShell::GetContext();
 
 	SUBCASE("RCSS document selectors")
 	{
@@ -183,11 +248,41 @@ TEST_CASE("Selectors")
 
 			String matching_ids;
 			GetMatchingIds(matching_ids, document);
+			CHECK_MESSAGE(matching_ids == selector.expected_ids, "Selector: " << selector.selector);
+
+			// Also check validity of selectors after structural document changes.
+			if (selector.operation != SelectorOp::None)
+			{
+				String operation_str;
+				switch (selector.operation)
+				{
+				case SelectorOp::RemoveElementsByIds:
+					RemoveElementsWithIds(document, selector.operation_argument);
+					operation_str = "RemoveElementsByIds";
+					break;
+				case SelectorOp::InsertElementBefore:
+					InsertElementBefore(document, selector.operation_argument);
+					operation_str = "InsertElementBefore";
+					break;
+				case SelectorOp::RemoveClasses:
+					RemoveClassesFromAllElements(document, selector.operation_argument);
+					operation_str = "RemoveClasses";
+					break;
+				case SelectorOp::RemoveChecked:
+					document->GetElementById(selector.operation_argument)->RemoveAttribute("checked");
+					operation_str = "RemoveChecked";
+					break;
+				case SelectorOp::None:
+					break;
+				}
+				context->Update();
 
-			if (!matching_ids.empty())
-				matching_ids.pop_back();
+				String matching_ids_after_operation;
+				GetMatchingIds(matching_ids_after_operation, document);
+				CHECK_MESSAGE(matching_ids_after_operation == selector.expected_ids_after_operation, "Selector: ", selector.selector,
+					"  Operation: ", operation_str, "  Argument: ", selector.operation_argument);
+			}
 
-			CHECK_MESSAGE(matching_ids == selector.expected_ids, "Selector: " << selector.selector);
 			context->UnloadDocument(document);
 		}
 	}
@@ -237,5 +332,5 @@ TEST_CASE("Selectors")
 		context->UnloadDocument(document);
 	}
 
-	Rml::Shutdown();
+	TestsShell::ShutdownShell();
 }