Browse Source

Add Node class

Element now derives from a new Node class. Node is responsible for managing the DOM tree, while Element is more closely tied to the RML nodes.

This opens up the possibility of having different nodes than just Element.

This change alone should be functionally equivalent and backward compatible. The intention is to later align naming and functionality to the HTML DOM. This will involve some breaking changes later on. Functions that are known to change in functionality later have been marked deprecated.
Michael Ragazzon 2 years ago
parent
commit
8d8b89a68a

+ 2 - 2
CMake/Utilities.cmake

@@ -103,9 +103,9 @@ function(set_common_target_options target)
 
 	if(RMLUI_COMPILER_OPTIONS)
 		if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
-			target_compile_options(${target} PRIVATE -Wall -Wextra -pedantic)
+			target_compile_options(${target} PRIVATE -Wall -Wextra -pedantic -Wno-deprecated-declarations)
 		elseif(MSVC)
-			target_compile_options(${target} PRIVATE /W4 /w44062 /wd4458 /wd4251 /permissive-)
+			target_compile_options(${target} PRIVATE /W4 /w44062 /wd4458 /wd4251 /wd4996 /permissive-)
 			target_compile_definitions(${target} PRIVATE _CRT_SECURE_NO_WARNINGS)
 			if(CMAKE_GENERATOR MATCHES "Visual Studio")
 				target_compile_options(${target} PRIVATE /MP)

+ 1 - 0
Include/RmlUi/Core.h

@@ -70,6 +70,7 @@
 #include "Core/Math.h"
 #include "Core/Mesh.h"
 #include "Core/MeshUtilities.h"
+#include "Core/Node.h"
 #include "Core/NumericValue.h"
 #include "Core/Plugin.h"
 #include "Core/PropertiesIteratorView.h"

+ 2 - 2
Include/RmlUi/Core/Context.h

@@ -368,8 +368,7 @@ private:
 	bool mouse_active;
 
 	// The current state of Touches, required to implement proper inertia scrolling.
-	struct TouchState
-	{
+	struct TouchState {
 		bool scrolling_right = false;
 		bool scrolling_down = false;
 		Vector2f start_position;
@@ -474,6 +473,7 @@ private:
 	// Sends the specified event to all elements in new_items that don't appear in old_items.
 	static void SendEvents(const ElementSet& old_items, const ElementSet& new_items, EventId id, const Dictionary& parameters);
 
+	friend class Rml::Node;
 	friend class Rml::Element;
 };
 

+ 18 - 55
Include/RmlUi/Core/Element.h

@@ -33,6 +33,7 @@
 #include "Core.h"
 #include "Event.h"
 #include "Header.h"
+#include "Node.h"
 #include "ObserverPtr.h"
 #include "Property.h"
 #include "RenderBox.h"
@@ -70,14 +71,14 @@ struct ElementMeta;
 struct StackingContextChild;
 
 /**
-	A generic element in the DOM tree.
+    A generic element in the DOM tree.
 
-	@author Peter Curry
+    @author Peter Curry
  */
 
-class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr<Element> {
+class RMLUICORE_API Element : public Node, public EnableObserverPtr<Element> {
 public:
-	RMLUI_RTTI_DefineWithParent(Element, ScriptInterface)
+	RMLUI_RTTI_DefineWithParent(Element, Node)
 
 	/// Constructs a new RmlUi element. This should not be called directly; use the Factory instead.
 	/// @param[in] tag The tag the element was declared as in RML.
@@ -346,13 +347,6 @@ public:
 	/// @return Outermost focus element.
 	Element* GetFocusLeafNode();
 
-	/// Returns the element's context.
-	/// @return The context this element's document exists within.
-	Context* GetContext() const;
-	/// Returns the element's render manager.
-	/// @return The render manager responsible for this element.
-	RenderManager* GetRenderManager() const;
-
 	/** @name DOM Properties
 	 */
 	//@{
@@ -429,13 +423,6 @@ public:
 	/// @return The element's style.
 	ElementStyle* GetStyle() const;
 
-	/// Gets the document this element belongs to.
-	/// @return This element's document.
-	ElementDocument* GetOwnerDocument() const;
-
-	/// Gets this element's parent node.
-	/// @return This element's parent.
-	Element* GetParentNode() const;
 	/// Recursively search for the first ancestor of this node matching the given selector.
 	/// @param[in] selectors The selector or comma-separated selectors to match against.
 	/// @return The ancestor if found, or nullptr if no ancestor could be matched.
@@ -444,17 +431,21 @@ public:
 
 	/// Gets the element immediately following this one in the tree.
 	/// @return This element's next sibling element, or nullptr if there is no sibling element.
-	Element* GetNextSibling() const;
+	Element* GetNextElementSibling() const;
+	[[deprecated("Use GetNextElementSibling")]] Element* GetNextSibling() const;
 	/// Gets the element immediately preceding this one in the tree.
 	/// @return This element's previous sibling element, or nullptr if there is no sibling element.
-	Element* GetPreviousSibling() const;
+	Element* GetPreviousElementSibling() const;
+	[[deprecated("Use GetPreviousElementSibling")]] Element* GetPreviousSibling() const;
 
 	/// Returns the first child of this element.
 	/// @return This element's first child, or nullptr if it contains no children.
-	Element* GetFirstChild() const;
+	Element* GetFirstElementChild() const;
+	[[deprecated("Use GetFirstElementChild")]] Element* GetFirstChild() const;
 	/// Gets the last child of this element.
 	/// @return This element's last child, or nullptr if it contains no children.
-	Element* GetLastChild() const;
+	Element* GetLastElementChild() const;
+	[[deprecated("Use GetLastElementChild")]] Element* GetLastChild() const;
 	/// Get the child element at the given index.
 	/// @param[in] index Index of child to get.
 	/// @return The child element at the given index.
@@ -531,28 +522,6 @@ public:
 	/// @note Smooth scrolling can only be applied to a single element at a time, any active smooth scrolls will be canceled.
 	void ScrollTo(Vector2f offset, ScrollBehavior behavior = ScrollBehavior::Instant);
 
-	/// Append a child to this element.
-	/// @param[in] element The element to append as a child.
-	/// @param[in] dom_element True if the element is to be part of the DOM, false otherwise. Only set this to false if you know what you're doing!
-	Element* AppendChild(ElementPtr element, bool dom_element = true);
-	/// Adds a child to this element directly before the adjacent element. The new element inherits the DOM/non-DOM
-	/// status from the adjacent element.
-	/// @param[in] element Element to be inserted.
-	/// @param[in] adjacent_element The reference element which the new element will be inserted before.
-	Element* InsertBefore(ElementPtr element, Element* adjacent_element);
-	/// Replaces the second node with the first node.
-	/// @param[in] inserted_element The element that will be inserted and replace the other element.
-	/// @param[in] replaced_element The existing element that will be replaced. If this doesn't exist, inserted_element will be appended.
-	/// @return A unique pointer to the replaced element if found, discard the result to immediately destroy.
-	ElementPtr ReplaceChild(ElementPtr inserted_element, Element* replaced_element);
-	/// Remove a child element from this element.
-	/// @param[in] element The element to remove.
-	/// @returns A unique pointer to the element if found, discard the result to immediately destroy.
-	ElementPtr RemoveChild(Element* element);
-	/// Returns whether or not this element has any DOM children.
-	/// @return True if the element has at least one DOM child, false otherwise.
-	bool HasChildNodes() const;
-
 	/// Get a child element by its ID.
 	/// @param[in] id The ID of the child element.
 	/// @return The child of this element with the given ID, or nullptr if no such child exists.
@@ -587,7 +556,7 @@ public:
 	//@}
 
 	/**
-		@name Internal Functions
+	    @name Internal Functions
 	 */
 	//@{
 	/// Access the event dispatcher for this element.
@@ -678,14 +647,14 @@ protected:
 	// Dirty the element style definition, including all descendants of the specified nodes.
 	void DirtyDefinition(DirtyNodes dirty_nodes);
 
-	void SetOwnerDocument(ElementDocument* document);
-
 	void OnStyleSheetChangeRecursive();
 
 	void Release() override;
 
 private:
-	void SetParent(Element* parent);
+	void OnChildNodeAdd(Node* child, bool dom_node) override;
+	void OnChildNodeRemove(Node* child, bool dom_node) override;
+	void OnParentChange(Node* parent_node) override;
 
 	void SetDataModel(DataModel* new_data_model);
 
@@ -752,9 +721,6 @@ private:
 	bool dirty_transform : 1;
 	bool dirty_perspective : 1;
 
-	OwnedElementList children;
-	int num_non_dom_children;
-
 	// Defines which box area to use for clipping; this is usually padding, but may be content.
 	BoxArea clip_area;
 
@@ -767,12 +733,8 @@ private:
 	// Instancer that created us, used for destruction.
 	ElementInstancer* instancer;
 
-	// Parent element.
-	Element* parent;
 	// Currently focused child object
 	Element* focus;
-	// The owning document
-	ElementDocument* owner_document;
 
 	// Active data model for this element.
 	DataModel* data_model;
@@ -815,6 +777,7 @@ private:
 
 	friend class Rml::Context;
 	friend class Rml::ElementStyle;
+	friend class Rml::Node;
 	friend class Rml::ContainerBox;
 	friend class Rml::InlineLevelBox;
 	friend class Rml::ReplacedBox;

+ 303 - 0
Include/RmlUi/Core/Node.h

@@ -0,0 +1,303 @@
+/*
+ * 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-2025 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_NODE_H
+#define RMLUI_CORE_NODE_H
+
+#include "ScriptInterface.h"
+#include "Traits.h"
+#include "Types.h"
+
+namespace Rml {
+
+class Context;
+class Element;
+class ElementDocument;
+using OwnedNodeList = Vector<NodePtr>;
+
+/**
+    A generic node in the DOM tree.
+ */
+class RMLUICORE_API Node : public ScriptInterface {
+public:
+	RMLUI_RTTI_DefineWithParent(Node, ScriptInterface)
+	virtual ~Node();
+
+	/// Get the child node at the given index.
+	/// @param[in] index Index of child to get.
+	/// @return The child node at the given index.
+	Node* GetChildNode(int index) const;
+	/// Get the number of children of this node.
+	/// @param[in] include_non_dom_elements True if the caller wants to include the non-DOM children. Only set this to true if you know what you're
+	/// doing!
+	/// @return The number of children.
+	int GetNumChildNodes(bool include_non_dom_elements = false) const;
+	/// Returns whether or not this node has any DOM children.
+	/// @return True if the node has at least one DOM child, false otherwise.
+	bool HasChildNodes() const;
+
+	/// Append a child to this node.
+	/// @param[in] node The node to append as a child.
+	/// @param[in] dom_node True if the node is to be part of the DOM, false otherwise. Only set this to false if you know what you're doing!
+	/// @return A pointer to the just inserted node.
+	Node* AppendChild(NodePtr node, bool dom_node = true);
+	[[deprecated("Use the NodePtr version of this function")]] Element* AppendChild(ElementPtr element, bool dom_element = true);
+	/// Adds a child to this node directly before the adjacent node. The new node inherits the DOM/non-DOM
+	/// status from the adjacent node.
+	/// @param[in] node Node to be inserted.
+	/// @param[in] adjacent_node The reference node which the new node will be inserted before.
+	/// @return A pointer to the just inserted node.
+	Node* InsertBefore(NodePtr node, Node* adjacent_node);
+	[[deprecated("Use the NodePtr version of this function")]] Element* InsertBefore(ElementPtr element, Element* adjacent_element);
+	/// Replaces the second node with the first node.
+	/// @param[in] insert_node The node to insert and replace the other node.
+	/// @param[in] replace_node The existing child node to replace. If this doesn't exist, insert_node will be appended.
+	/// @return A unique pointer to the replaced node if found, discard the result to immediately destroy.
+	NodePtr ReplaceChild(NodePtr insert_node, Node* replace_node);
+	[[deprecated("Use the NodePtr version of this function")]] ElementPtr ReplaceChild(ElementPtr inserted_element, Element* replaced_element);
+	/// Remove a child node from this node.
+	/// @param[in] node The node to remove.
+	/// @return A unique pointer to the node if found, discard the result to immediately destroy.
+	NodePtr RemoveChild(Node* node);
+	[[deprecated("Use the NodePtr version of this function")]] ElementPtr RemoveChild(Element* element);
+
+	/// Gets this nodes's parent node.
+	/// @return This node's parent.
+	[[deprecated("Use GetParentElement")]] Element* GetParentNode() const;
+	/// Gets this nodes's parent element.
+	/// @return This node's parent if it is an element, otherwise false.
+	Element* GetParentElement() const;
+	/// Gets the document this node belongs to.
+	/// @return This node's document.
+	ElementDocument* GetOwnerDocument() const;
+
+	/// Returns the element's context.
+	/// @return The context this element's document exists within.
+	Context* GetContext() const;
+	/// Returns the element's render manager.
+	/// @return The render manager responsible for this element.
+	RenderManager* GetRenderManager() const;
+
+	template <typename T>
+	class NodeChildIterator {
+	public:
+		using iterator_category = std::forward_iterator_tag;
+		using value_type = T*;
+		using difference_type = std::ptrdiff_t;
+		using pointer = T**;
+		using reference = T*&;
+
+		struct Stop {};
+
+		NodeChildIterator() = default;
+		NodeChildIterator(const Node* host, int index, bool include_non_dom_elements) :
+			host(host), index(index), include_non_dom_elements(include_non_dom_elements)
+		{
+			Advance();
+		}
+
+		T* operator*() const
+		{
+			RMLUI_ASSERT(host && index < (int)host->children.size());
+			RMLUI_ASSERTMSG(include_non_dom_elements || index < (int)host->children.size() - host->num_non_dom_children,
+				"Attempting to access a non-DOM child, but configured to exclude them.");
+			return rmlui_static_cast<T*>(host->children[index].get());
+		}
+
+		NodeChildIterator& operator++()
+		{
+			++index;
+			Advance();
+			return *this;
+		}
+
+		NodeChildIterator operator++(int)
+		{
+			NodeChildIterator it = *this;
+			++*this;
+			return it;
+		}
+
+		bool operator==(const Stop& /*stop*/) const
+		{
+			return !host || index >= (int)host->children.size() - (include_non_dom_elements ? 0 : host->num_non_dom_children);
+		}
+		bool operator!=(const Stop& stop) const { return !(*this == stop); }
+
+		bool operator==(const NodeChildIterator& other) const { return host == other.host && index == other.index; }
+		bool operator!=(const NodeChildIterator& other) const { return !(*this == other); }
+
+	private:
+		const Node* host = nullptr;
+		int index = 0;
+		bool include_non_dom_elements = false;
+
+		void Advance()
+		{
+			const int size = (int)host->children.size();
+			while (index < size)
+			{
+				if (rmlui_dynamic_cast<T*>(host->children[index].get()))
+					return;
+				++index;
+			}
+
+			if (!include_non_dom_elements && index >= size - host->num_non_dom_children)
+			{
+				index = size;
+			}
+		}
+	};
+
+	template <typename T>
+	class NodeChildReverseIterator {
+	public:
+		using iterator_category = std::forward_iterator_tag;
+		using value_type = T*;
+		using difference_type = std::ptrdiff_t;
+		using pointer = T**;
+		using reference = T*&;
+
+		struct Stop {};
+
+		NodeChildReverseIterator() = default;
+		NodeChildReverseIterator(const Node* host, int index, bool include_non_dom_elements) :
+			host(host), index(index), include_non_dom_elements(include_non_dom_elements)
+		{
+			Advance();
+		}
+
+		T* operator*() const
+		{
+			RMLUI_ASSERT(host && index >= 0 && index < (int)host->children.size());
+			RMLUI_ASSERTMSG(include_non_dom_elements || index < (int)host->children.size() - host->num_non_dom_children,
+				"Attempting to access a non-DOM child, but configured to exclude them.");
+			return rmlui_static_cast<T*>(host->children[index].get());
+		}
+
+		NodeChildReverseIterator& operator++()
+		{
+			--index;
+			Advance();
+			return *this;
+		}
+
+		NodeChildReverseIterator operator++(int)
+		{
+			NodeChildIterator it = *this;
+			--*this;
+			return it;
+		}
+
+		bool operator==(const Stop& /*stop*/) const { return !host || index < 0; }
+		bool operator!=(const Stop& stop) const { return !(*this == stop); }
+
+		bool operator==(const NodeChildReverseIterator& other) const { return host == other.host && index == other.index; }
+		bool operator!=(const NodeChildReverseIterator& other) const { return !(*this == other); }
+
+	private:
+		const Node* host = nullptr;
+		int index = 0;
+		bool include_non_dom_elements = false;
+
+		void Advance()
+		{
+			while (index >= 0)
+			{
+				if (rmlui_dynamic_cast<T*>(host->children[index].get()))
+					return;
+				--index;
+			}
+		}
+	};
+
+	template <typename T>
+	class NodeChildRange {
+	public:
+		NodeChildRange() = default;
+		NodeChildRange(const Node* node, bool include_non_dom_elements) : node(node), include_non_dom_elements(include_non_dom_elements) {}
+		NodeChildIterator<T> begin() { return {node, 0, include_non_dom_elements}; }
+		typename NodeChildIterator<T>::Stop end() { return {}; }
+
+	private:
+		const Node* node = nullptr;
+		bool include_non_dom_elements = false;
+	};
+
+	template <typename T>
+	class NodeChildReverseRange {
+	public:
+		NodeChildReverseRange() = default;
+		NodeChildReverseRange(const Node* node, bool include_non_dom_elements) : node(node), include_non_dom_elements(include_non_dom_elements) {}
+		NodeChildReverseIterator<T> begin()
+		{
+			return {node, (int)node->children.size() - 1 - (include_non_dom_elements ? 0 : node->num_non_dom_children), include_non_dom_elements};
+		}
+		typename NodeChildReverseIterator<T>::Stop end() { return {}; }
+
+	private:
+		const Node* node = nullptr;
+		bool include_non_dom_elements = false;
+	};
+
+	template <typename T>
+	NodeChildRange<T> IterateChildren(bool include_non_dom_elements = false) const
+	{
+		return NodeChildRange<T>(this, include_non_dom_elements);
+	}
+
+	template <typename T>
+	NodeChildReverseRange<T> IterateChildrenReverse(bool include_non_dom_elements = false) const
+	{
+		return NodeChildReverseRange<T>(this, include_non_dom_elements);
+	}
+
+protected:
+	Node();
+
+	void SetOwnerDocument(ElementDocument* document);
+
+	virtual void OnChildNodeAdd(Node* child, bool dom_node);
+	virtual void OnChildNodeRemove(Node* child, bool dom_node);
+	virtual void OnParentChange(Node* parent);
+
+private:
+	void SetParent(Node* new_parent);
+
+	Node* parent = nullptr;
+	ElementDocument* owner_document = nullptr;
+
+	OwnedNodeList children;
+	int num_non_dom_children = 0;
+
+	friend class Rml::Context;
+};
+
+} // namespace Rml
+
+#endif

+ 29 - 0
Include/RmlUi/Core/Types.h

@@ -76,6 +76,7 @@ using RowMajorMatrix4f = Matrix4<float, RowMajorStorage<float>>;
 using Matrix4f = RMLUI_MATRIX4_TYPE;
 
 // Common classes
+class Node;
 class Element;
 class ElementInstancer;
 class ElementAnimation;
@@ -114,6 +115,7 @@ using FontFaceHandle = uintptr_t;
 using FontEffectsHandle = uintptr_t;
 using LayerHandle = uintptr_t;
 
+using NodePtr = UniqueReleaserPtr<Node>;
 using ElementPtr = UniqueReleaserPtr<Element>;
 using ContextPtr = UniqueReleaserPtr<Context>;
 using EventPtr = UniqueReleaserPtr<Event>;
@@ -195,4 +197,31 @@ struct hash<::Rml::FamilyId> {
 };
 } // namespace std
 
+namespace Rml {
+
+template <typename T, typename U>
+inline T As(U base_instance)
+{
+	if constexpr (std::is_same<T, NodePtr>::value && std::is_same<U, ElementPtr>::value)
+	{
+		return NodePtr(static_cast<Node*>(base_instance.release()));
+	}
+	else if constexpr (std::is_same<T, ElementPtr>::value && std::is_same<U, NodePtr>::value)
+	{
+		return ElementPtr(rmlui_static_cast<Element*>(base_instance.release()));
+	}
+	else
+	{
+		return rmlui_static_cast<T>(base_instance);
+	}
+}
+
+template <typename T, typename U>
+inline T AsIf(U base_instance)
+{
+	return rmlui_dynamic_cast<T>(base_instance);
+}
+
+} // namespace Rml
+
 #endif

+ 2 - 0
Source/Core/CMakeLists.txt

@@ -124,6 +124,7 @@ add_library(rmlui_core
 	Memory.cpp
 	Memory.h
 	MeshUtilities.cpp
+	Node.cpp
 	ObserverPtr.cpp
 	Plugin.cpp
 	PluginRegistry.cpp
@@ -295,6 +296,7 @@ target_sources(rmlui_core PRIVATE
 	"${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.inl"
 	"${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Mesh.h"
 	"${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/MeshUtilities.h"
+	"${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Node.h"
 	"${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/NumericValue.h"
 	"${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ObserverPtr.h"
 	"${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Platform.h"

+ 11 - 9
Source/Core/Context.cpp

@@ -498,9 +498,9 @@ void Context::PullDocumentToFront(ElementDocument* document)
 		{
 			if (root->GetChild(i) == document)
 			{
-				ElementPtr element = std::move(root->children[i]);
+				NodePtr node = std::move(root->children[i]);
 				root->children.erase(root->children.begin() + i);
-				root->children.insert(root->children.begin() + root->GetNumChildren(), std::move(element));
+				root->children.insert(root->children.begin() + root->GetNumChildren(), std::move(node));
 
 				root->DirtyStackingContext();
 			}
@@ -517,9 +517,9 @@ void Context::PushDocumentToBack(ElementDocument* document)
 		{
 			if (root->GetChild(i) == document)
 			{
-				ElementPtr element = std::move(root->children[i]);
+				NodePtr node = std::move(root->children[i]);
 				root->children.erase(root->children.begin() + i);
-				root->children.insert(root->children.begin(), std::move(element));
+				root->children.insert(root->children.begin(), std::move(node));
 
 				root->DirtyStackingContext();
 			}
@@ -919,7 +919,7 @@ bool Context::ProcessTouchCancel(const TouchList& touches)
 
 bool Context::ProcessTouchStart(const Touch& touch, int key_modifier_state)
 {
-	TouchState * state = LookupTouch(touch.identifier);
+	TouchState* state = LookupTouch(touch.identifier);
 	RMLUI_ASSERTMSG(state == nullptr, "Receiving touch start event for an already started touch.");
 	if (!state)
 	{
@@ -972,7 +972,8 @@ bool Context::ProcessTouchMove(const Touch& touch, int key_modifier_state)
 
 				// If the user changes direction, reset the start time and position.
 				bool going_right = (delta.x > 0);
-				if (delta.x != 0 && (going_right != state->scrolling_right ||
+				if (delta.x != 0 &&
+					(going_right != state->scrolling_right ||
 						state->scrolling_start_time_x == 0)) // time set to 0 means no touch move events happened before and direction is unclear
 				{
 					state->start_position.x = touch.position.x;
@@ -981,7 +982,7 @@ bool Context::ProcessTouchMove(const Touch& touch, int key_modifier_state)
 				}
 				else
 				{
-					// move starting position towards end position with a weight of e^-kt to better capture 
+					// move starting position towards end position with a weight of e^-kt to better capture
 					// and calculate velocity of the very last touch movements before touch release
 					float elapsed_time_x = static_cast<float>(current_time - state->scrolling_start_time_x);
 					float weight = Math::Exp(-elapsed_time_x * TOUCH_MOVEMENT_DECAY_RATE);
@@ -991,7 +992,8 @@ bool Context::ProcessTouchMove(const Touch& touch, int key_modifier_state)
 				}
 
 				bool going_down = (delta.y > 0);
-				if (delta.y != 0 && (going_down != state->scrolling_down ||
+				if (delta.y != 0 &&
+					(going_down != state->scrolling_down ||
 						state->scrolling_start_time_y == 0)) // time set to 0 means no touch move events happened before and direction is unclear
 				{
 					state->start_position.y = touch.position.y;
@@ -1007,7 +1009,7 @@ bool Context::ProcessTouchMove(const Touch& touch, int key_modifier_state)
 
 					state->start_position.y = touch.position.y - (touch.position.y - state->start_position.y) * weight;
 					state->scrolling_start_time_y = current_time - (current_time - state->scrolling_start_time_y) * weight;
-				}	
+				}
 			}
 		}
 	}

+ 182 - 321
Source/Core/Element.cpp

@@ -99,18 +99,14 @@ Element::Element(const String& tag) :
 	relative_offset_base(0, 0), relative_offset_position(0, 0), absolute_offset(0, 0), scroll_offset(0, 0)
 {
 	RMLUI_ASSERT(tag == StringUtilities::ToLower(tag));
-	parent = nullptr;
 	focus = nullptr;
 	instancer = nullptr;
-	owner_document = nullptr;
 	offset_parent = nullptr;
 
 	clip_area = BoxArea::Padding;
 
 	baseline = 0.0f;
 
-	num_non_dom_children = 0;
-
 	z_index = 0;
 
 	meta = ElementMetaPool::element_meta_pool->pool.AllocateAndConstruct(this);
@@ -119,23 +115,16 @@ Element::Element(const String& tag) :
 
 Element::~Element()
 {
-	RMLUI_ASSERT(parent == nullptr);
-
+	RMLUI_ASSERT(GetParentNode() == nullptr);
 	PluginRegistry::NotifyElementDestroy(this);
 
-	// A simplified version of RemoveChild() for destruction.
-	for (ElementPtr& child : children)
+	for (Element* child : IterateChildren<Element>(true))
 	{
-		Element* child_ancestor = child.get();
-		for (int i = 0; i <= ChildNotifyLevels && child_ancestor; i++, child_ancestor = child_ancestor->GetParentNode())
-			child_ancestor->OnChildRemove(child.get());
-
-		child->SetParent(nullptr);
+		Element* child_ancestor = child;
+		for (int i = 0; i <= ChildNotifyLevels && child_ancestor; i++, child_ancestor = child_ancestor->GetParentElement())
+			child_ancestor->OnChildRemove(child);
 	}
 
-	children.clear();
-	num_non_dom_children = 0;
-
 	ElementMetaPool::element_meta_pool->pool.DestroyAndDeallocate(meta);
 }
 
@@ -167,8 +156,8 @@ void Element::Update(float dp_ratio, Vector2f vp_dimensions)
 
 	meta->effects.InstanceEffects();
 
-	for (size_t i = 0; i < children.size(); i++)
-		children[i]->Update(dp_ratio, vp_dimensions);
+	for (Element* child : IterateChildren<Element>(true))
+		child->Update(dp_ratio, vp_dimensions);
 
 	if (!animations.empty() && IsVisible(true))
 	{
@@ -183,7 +172,9 @@ void Element::UpdateProperties(const float dp_ratio, const Vector2f vp_dimension
 
 	if (meta->style.AnyPropertiesDirty())
 	{
+		Element* parent = GetParentElement();
 		const ComputedValues* parent_values = parent ? &parent->GetComputedValues() : nullptr;
+		ElementDocument* owner_document = GetOwnerDocument();
 		const ComputedValues* document_values = owner_document ? &owner_document->GetComputedValues() : nullptr;
 
 		// Compute values and clear dirty properties
@@ -335,10 +326,10 @@ String Element::GetAddress(bool include_pseudo_classes, bool include_parents) co
 		}
 	}
 
-	if (include_parents && parent)
+	if (include_parents && GetParentElement())
 	{
 		address += " < ";
-		return address + parent->GetAddress(include_pseudo_classes, true);
+		return address + GetParentElement()->GetAddress(include_pseudo_classes, true);
 	}
 	else
 		return address;
@@ -402,7 +393,7 @@ void Element::UpdateAbsoluteOffsetAndRenderBoxData()
 				offset_from_ancestors -= offset_parent->scroll_offset;
 
 			// Finally, there may be relatively positioned elements between ourself and our containing block, add their relative offsets as well.
-			for (Element* ancestor = parent; ancestor && ancestor != offset_parent; ancestor = ancestor->parent)
+			for (Element* ancestor = GetParentElement(); ancestor && ancestor != offset_parent; ancestor = ancestor->GetParentElement())
 				offset_from_ancestors += ancestor->relative_offset_position;
 		}
 
@@ -592,7 +583,7 @@ bool Element::IsVisible(bool include_ancestors) const
 	{
 		if (!element->visible)
 			return false;
-		element = element->parent;
+		element = element->GetParentElement();
 	}
 	return true;
 }
@@ -879,22 +870,6 @@ Element* Element::GetFocusLeafNode()
 	return focus_element;
 }
 
-Context* Element::GetContext() const
-{
-	ElementDocument* document = GetOwnerDocument();
-	if (document != nullptr)
-		return document->GetContext();
-
-	return nullptr;
-}
-
-RenderManager* Element::GetRenderManager() const
-{
-	if (Context* context = GetContext())
-		return &context->GetRenderManager();
-	return nullptr;
-}
-
 void Element::SetAttributes(const ElementAttributes& _attributes)
 {
 	attributes.reserve(attributes.size() + _attributes.size());
@@ -1030,26 +1005,6 @@ ElementStyle* Element::GetStyle() const
 	return &meta->style;
 }
 
-ElementDocument* Element::GetOwnerDocument() const
-{
-#ifdef RMLUI_DEBUG
-	if (parent && !owner_document)
-	{
-		// Since we have a parent but no owner_document, then we must be a 'loose' element -- that is, constructed
-		// outside of a document and not attached to a child of any element in the hierarchy of a document.
-		// This check ensures that we didn't just forget to set the owner document.
-		RMLUI_ASSERT(!parent->GetOwnerDocument());
-	}
-#endif
-
-	return owner_document;
-}
-
-Element* Element::GetParentNode() const
-{
-	return parent;
-}
-
 Element* Element::Closest(const String& selectors) const
 {
 	StyleSheetNode root_node;
@@ -1079,68 +1034,109 @@ Element* Element::Closest(const String& selectors) const
 	return nullptr;
 }
 
-Element* Element::GetNextSibling() const
+Element* Element::GetNextElementSibling() const
 {
-	if (parent == nullptr)
+	Element* parent = GetParentElement();
+	if (!parent)
 		return nullptr;
 
-	for (size_t i = 0; i < parent->children.size() - (parent->num_non_dom_children + 1); i++)
+	bool return_next = false;
+	for (Element* sibling : parent->IterateChildren<Element>())
 	{
-		if (parent->children[i].get() == this)
-			return parent->children[i + 1].get();
+		if (return_next)
+			return sibling;
+		if (sibling == this)
+			return_next = true;
 	}
 
 	return nullptr;
 }
 
-Element* Element::GetPreviousSibling() const
+Element* Element::GetPreviousElementSibling() const
 {
-	if (parent == nullptr)
+	Element* parent = GetParentElement();
+	if (!parent)
 		return nullptr;
 
-	for (size_t i = 1; i < parent->children.size() - parent->num_non_dom_children; i++)
+	Element* previous_sibling = nullptr;
+	for (Element* sibling : parent->IterateChildren<Element>())
 	{
-		if (parent->children[i].get() == this)
-			return parent->children[i - 1].get();
+		if (sibling == this)
+			return previous_sibling;
+		previous_sibling = sibling;
 	}
 
 	return nullptr;
 }
 
+Element* Element::GetNextSibling() const
+{
+	return GetNextElementSibling();
+}
+
+Element* Element::GetPreviousSibling() const
+{
+	return GetPreviousElementSibling();
+}
+Element* Element::GetFirstElementChild() const
+{
+	auto range = IterateChildren<Element>();
+	auto it = range.begin();
+	if (it == range.end())
+		return nullptr;
+	return *it;
+}
+
 Element* Element::GetFirstChild() const
 {
-	if (GetNumChildren() > 0)
-		return children[0].get();
+	return GetFirstElementChild();
+}
 
-	return nullptr;
+Element* Element::GetLastElementChild() const
+{
+	auto range = IterateChildrenReverse<Element>();
+	auto it = range.begin();
+	if (it == range.end())
+		return nullptr;
+	return *it;
 }
 
 Element* Element::GetLastChild() const
 {
-	if (GetNumChildren() > 0)
-		return (children.end() - (num_non_dom_children + 1))->get();
-
-	return nullptr;
+	return GetLastElementChild();
 }
 
 Element* Element::GetChild(int index) const
 {
-	if (index < 0 || index >= (int)children.size())
-		return nullptr;
-
-	return children[index].get();
+	int i_element = 0;
+	for (Element* child : IterateChildren<Element>(true))
+	{
+		if (i_element == index)
+			return child;
+		i_element += 1;
+	}
+	return nullptr;
 }
 
 int Element::GetNumChildren(bool include_non_dom_elements) const
 {
-	return (int)children.size() - (include_non_dom_elements ? 0 : num_non_dom_children);
+	int num_elements = 0;
+	for (Element* child : IterateChildren<Element>(include_non_dom_elements))
+	{
+		num_elements += 1;
+		(void)child;
+	}
+	return num_elements;
 }
 
 void Element::GetInnerRML(String& content) const
 {
-	for (int i = 0; i < GetNumChildren(); i++)
+	for (Node* child : IterateChildren<Node>())
 	{
-		children[i]->GetRML(content);
+		if (Element* element = AsIf<Element*>(child))
+			element->GetRML(content);
+		else if (ElementText* text = AsIf<ElementText*>(child))
+			content += StringUtilities::EncodeRml(text->GetText());
 	}
 }
 
@@ -1156,8 +1152,8 @@ void Element::SetInnerRML(const String& rml)
 	RMLUI_ZoneScopedC(0x6495ED);
 
 	// Remove all DOM children.
-	while ((int)children.size() > num_non_dom_children)
-		RemoveChild(children.front().get());
+	while (GetNumChildNodes() > 0)
+		RemoveChild(GetChildNode(0));
 
 	if (!rml.empty())
 		Factory::InstanceElementText(this, rml);
@@ -1194,7 +1190,7 @@ bool Element::Focus(bool focus_visible)
 
 void Element::Blur()
 {
-	if (parent)
+	if (Element* parent = GetParentElement())
 	{
 		Context* context = GetContext();
 		if (context == nullptr)
@@ -1267,7 +1263,7 @@ void Element::ScrollIntoView(const ScrollIntoViewOptions options)
 	const Vector2f size = main_box.GetSize(BoxArea::Border);
 	ScrollBehavior scroll_behavior = options.behavior;
 
-	for (Element* scroll_parent = parent; scroll_parent; scroll_parent = scroll_parent->GetParentNode())
+	for (Element* scroll_parent = GetParentElement(); scroll_parent; scroll_parent = scroll_parent->GetParentNode())
 	{
 		using Style::Overflow;
 		const ComputedValues& computed = scroll_parent->GetComputedValues();
@@ -1326,176 +1322,6 @@ void Element::ScrollTo(Vector2f offset, ScrollBehavior behavior)
 	SetScrollTop(offset.y);
 }
 
-Element* Element::AppendChild(ElementPtr child, bool dom_element)
-{
-	RMLUI_ASSERT(child);
-	Element* child_ptr = child.get();
-	if (dom_element)
-		children.insert(children.end() - num_non_dom_children, std::move(child));
-	else
-	{
-		children.push_back(std::move(child));
-		num_non_dom_children++;
-	}
-	// Set parent just after inserting into children. This allows us to eg. get our previous sibling in SetParent.
-	child_ptr->SetParent(this);
-
-	Element* ancestor = child_ptr;
-	for (int i = 0; i <= ChildNotifyLevels && ancestor; i++, ancestor = ancestor->GetParentNode())
-		ancestor->OnChildAdd(child_ptr);
-
-	DirtyStackingContext();
-
-	// Not only does the element definition of the newly inserted element need to be dirtied, but also our own definition and implicitly all of our
-	// children's. This ensures correct styles being applied in the presence of tree-structural selectors such as ':first-child'.
-	DirtyDefinition(DirtyNodes::Self);
-
-	if (dom_element)
-		DirtyLayout();
-
-	return child_ptr;
-}
-
-Element* Element::InsertBefore(ElementPtr child, Element* adjacent_element)
-{
-	RMLUI_ASSERT(child);
-	// Find the position in the list of children of the adjacent element. If
-	// it's nullptr or we can't find it, then we insert it at the end of the dom
-	// children, as a dom element.
-	size_t child_index = 0;
-	bool found_child = false;
-	if (adjacent_element)
-	{
-		for (child_index = 0; child_index < children.size(); child_index++)
-		{
-			if (children[child_index].get() == adjacent_element)
-			{
-				found_child = true;
-				break;
-			}
-		}
-	}
-
-	Element* child_ptr = nullptr;
-
-	if (found_child)
-	{
-		child_ptr = child.get();
-
-		if ((int)child_index >= GetNumChildren())
-			num_non_dom_children++;
-		else
-			DirtyLayout();
-
-		children.insert(children.begin() + child_index, std::move(child));
-		child_ptr->SetParent(this);
-
-		Element* ancestor = child_ptr;
-		for (int i = 0; i <= ChildNotifyLevels && ancestor; i++, ancestor = ancestor->GetParentNode())
-			ancestor->OnChildAdd(child_ptr);
-
-		DirtyStackingContext();
-		DirtyDefinition(DirtyNodes::Self);
-	}
-	else
-	{
-		child_ptr = AppendChild(std::move(child));
-	}
-
-	return child_ptr;
-}
-
-ElementPtr Element::ReplaceChild(ElementPtr inserted_element, Element* replaced_element)
-{
-	RMLUI_ASSERT(inserted_element);
-	auto insertion_point = children.begin();
-	while (insertion_point != children.end() && insertion_point->get() != replaced_element)
-	{
-		++insertion_point;
-	}
-
-	Element* inserted_element_ptr = inserted_element.get();
-
-	if (insertion_point == children.end())
-	{
-		AppendChild(std::move(inserted_element));
-		return nullptr;
-	}
-
-	children.insert(insertion_point, std::move(inserted_element));
-	inserted_element_ptr->SetParent(this);
-
-	ElementPtr result = RemoveChild(replaced_element);
-
-	Element* ancestor = inserted_element_ptr;
-	for (int i = 0; i <= ChildNotifyLevels && ancestor; i++, ancestor = ancestor->GetParentNode())
-		ancestor->OnChildAdd(inserted_element_ptr);
-
-	return result;
-}
-
-ElementPtr Element::RemoveChild(Element* child)
-{
-	size_t child_index = 0;
-
-	for (auto itr = children.begin(); itr != children.end(); ++itr)
-	{
-		// Add the element to the delete list
-		if (itr->get() == child)
-		{
-			Element* ancestor = child;
-			for (int i = 0; i <= ChildNotifyLevels && ancestor; i++, ancestor = ancestor->GetParentNode())
-				ancestor->OnChildRemove(child);
-
-			if (child_index >= children.size() - num_non_dom_children)
-				num_non_dom_children--;
-
-			ElementPtr detached_child = std::move(*itr);
-			children.erase(itr);
-
-			// Remove the child element as the focused child of this element.
-			if (child == focus)
-			{
-				focus = nullptr;
-
-				// If this child (or a descendant of this child) is the context's currently
-				// focused element, set the focus to us instead.
-				if (Context* context = GetContext())
-				{
-					Element* focus_element = context->GetFocusElement();
-					while (focus_element)
-					{
-						if (focus_element == child)
-						{
-							Focus();
-							break;
-						}
-
-						focus_element = focus_element->GetParentNode();
-					}
-				}
-			}
-
-			detached_child->SetParent(nullptr);
-
-			DirtyLayout();
-			DirtyStackingContext();
-			DirtyDefinition(DirtyNodes::Self);
-
-			return detached_child;
-		}
-
-		child_index++;
-	}
-
-	return nullptr;
-}
-
-bool Element::HasChildNodes() const
-{
-	return (int)children.size() > num_non_dom_children;
-}
-
 Element* Element::GetElementById(const String& id)
 {
 	// Check for special-case tokens.
@@ -1504,7 +1330,7 @@ Element* Element::GetElementById(const String& id)
 	else if (id == "#document")
 		return GetOwnerDocument();
 	else if (id == "#parent")
-		return this->parent;
+		return GetParentElement();
 	else
 	{
 		Element* search_root = GetOwnerDocument();
@@ -1841,7 +1667,7 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
 		{
 			visible = new_visibility;
 
-			if (parent != nullptr)
+			if (Element* parent = GetParentElement())
 				parent->DirtyStackingContext();
 
 			if (!visible)
@@ -1882,7 +1708,7 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
 			}
 
 			// When our z-index or local stacking context changes, then we must dirty our parent stacking context so we are re-indexed.
-			if (parent)
+			if (Element* parent = GetParentElement())
 				parent->DirtyStackingContext();
 		}
 	}
@@ -1994,7 +1820,7 @@ Element* Element::GetClosestScrollableContainer()
 
 	if (scrollable_x || scrollable_y || meta->computed_values.overscroll_behavior() == OverscrollBehavior::Contain)
 		return this;
-	else if (parent)
+	else if (Element* parent = GetParentElement())
 		return parent->GetClosestScrollableContainer();
 
 	return nullptr;
@@ -2094,24 +1920,6 @@ void Element::GetRML(String& content)
 	}
 }
 
-void Element::SetOwnerDocument(ElementDocument* document)
-{
-	if (owner_document && !document)
-	{
-		// We are detaching from the document and thereby also the context.
-		if (Context* context = owner_document->GetContext())
-			context->OnElementDetach(this);
-	}
-
-	// If this element is a document, then never change owner_document.
-	if (owner_document != this && owner_document != document)
-	{
-		owner_document = document;
-		for (ElementPtr& child : children)
-			child->SetOwnerDocument(document);
-	}
-}
-
 void Element::SetDataModel(DataModel* new_data_model)
 {
 	RMLUI_ASSERTMSG(!data_model || !new_data_model, "We must either attach a new data model, or detach the old one.");
@@ -2131,7 +1939,7 @@ void Element::SetDataModel(DataModel* new_data_model)
 	if (data_model)
 		ElementUtilities::ApplyDataViewsControllers(this);
 
-	for (ElementPtr& child : children)
+	for (Element* child : IterateChildren<Element>(true))
 		child->SetDataModel(new_data_model);
 }
 
@@ -2143,50 +1951,99 @@ void Element::Release()
 		Log::Message(Log::LT_WARNING, "Leak detected: element %s not instanced via RmlUi Factory. Unable to release.", GetAddress().c_str());
 }
 
-void Element::SetParent(Element* _parent)
+void Element::OnChildNodeAdd(Node* child_node, bool dom_node)
 {
-	// Assumes we are already detached from the hierarchy or we are detaching now.
-	RMLUI_ASSERT(!parent || !_parent);
+	Element* child = AsIf<Element*>(child_node);
+	Element* ancestor = child;
+	for (int i = 0; i <= ChildNotifyLevels && ancestor; i++, ancestor = ancestor->GetParentElement())
+		ancestor->OnChildAdd(child);
+
+	DirtyStackingContext();
+
+	// Not only does the element definition of the newly inserted element need to be dirtied, but also our own definition and implicitly all of our
+	// children's. This ensures correct styles being applied in the presence of tree-structural selectors such as ':first-child'.
+	DirtyDefinition(DirtyNodes::Self);
 
-	parent = _parent;
+	if (dom_node)
+		DirtyLayout();
+}
 
-	if (parent)
+void Element::OnChildNodeRemove(Node* child_node, bool dom_node)
+{
+	Element* child = AsIf<Element*>(child_node);
+	Element* ancestor = child;
+	for (int i = 0; i <= ChildNotifyLevels && ancestor; i++, ancestor = ancestor->GetParentElement())
+		ancestor->OnChildRemove(child);
+
+	// Remove the child element as the focused child of this element.
+	if (child && child == focus)
 	{
-		// We need to update our definition and make sure we inherit the properties of our new parent.
-		DirtyDefinition(DirtyNodes::Self);
-		meta->style.DirtyInheritedProperties();
-	}
+		focus = nullptr;
 
-	// The transform state may require recalculation.
-	if (transform_state || (parent && parent->transform_state))
-		DirtyTransformState(true, true);
+		// If this child (or a descendant of this child) is the context's currently
+		// focused element, set the focus to us instead.
+		if (Context* context = GetContext())
+		{
+			Element* focus_element = context->GetFocusElement();
+			while (focus_element)
+			{
+				if (focus_element == child)
+				{
+					Focus();
+					break;
+				}
 
-	SetOwnerDocument(parent ? parent->GetOwnerDocument() : nullptr);
+				focus_element = focus_element->GetParentElement();
+			}
+		}
+	}
 
-	if (!parent)
+	DirtyStackingContext();
+	DirtyDefinition(DirtyNodes::Self);
+	if (dom_node)
+		DirtyLayout();
+}
+
+void Element::OnParentChange(Node* parent_node)
+{
+	if (!parent_node)
 	{
 		if (data_model)
 			SetDataModel(nullptr);
+
+		return;
 	}
-	else
+
+	Element* parent = As<Element*>(parent_node);
+	if (!parent)
 	{
-		auto it = attributes.find("data-model");
-		if (it == attributes.end())
-		{
-			SetDataModel(parent->data_model);
-		}
-		else if (Context* context = GetContext())
-		{
-			String name = it->second.Get<String>();
+		// New parent is a non-element node.
+		return;
+	}
 
-			if (DataModel* model = context->GetDataModelPtr(name))
-			{
-				model->AttachModelRootElement(this);
-				SetDataModel(model);
-			}
-			else
-				Log::Message(Log::LT_ERROR, "Could not locate data model '%s' in element %s.", name.c_str(), GetAddress().c_str());
+	// We need to update our definition and make sure we inherit the properties of our new parent.
+	DirtyDefinition(DirtyNodes::Self);
+	meta->style.DirtyInheritedProperties();
+
+	// The transform state may require recalculation.
+	DirtyTransformState(true, true);
+
+	auto it = attributes.find("data-model");
+	if (it == attributes.end())
+	{
+		SetDataModel(parent->data_model);
+	}
+	else if (Context* context = GetContext())
+	{
+		String name = it->second.Get<String>();
+
+		if (DataModel* model = context->GetDataModelPtr(name))
+		{
+			model->AttachModelRootElement(this);
+			SetDataModel(model);
 		}
+		else
+			Log::Message(Log::LT_ERROR, "Could not locate data model '%s' in element %s.", name.c_str(), GetAddress().c_str());
 	}
 }
 
@@ -2206,8 +2063,8 @@ void Element::DirtyAbsoluteOffsetRecursive()
 			DirtyTransformState(true, true);
 	}
 
-	for (size_t i = 0; i < children.size(); i++)
-		children[i]->DirtyAbsoluteOffsetRecursive();
+	for (Element* child : IterateChildren<Element>(true))
+		child->DirtyAbsoluteOffsetRecursive();
 }
 
 void Element::UpdateOffset()
@@ -2345,11 +2202,15 @@ void Element::BuildLocalStackingContext()
 void Element::AddChildrenToStackingContext(Vector<StackingContextChild>& stacking_children)
 {
 	bool is_flex_container = (GetDisplay() == Style::Display::Flex);
-	const int num_children = (int)children.size();
-	for (int i = 0; i < num_children; ++i)
+	const int num_children_dom = GetNumChildNodes();
+	const int num_children_all = GetNumChildNodes(true);
+	for (int i = 0; i < num_children_all; ++i)
 	{
-		const bool is_non_dom_element = (i >= num_children - num_non_dom_children);
-		children[i]->AddToStackingContext(stacking_children, is_flex_container, is_non_dom_element);
+		if (Element* child = AsIf<Element*>(GetChildNode(i)))
+		{
+			const bool is_non_dom_element = (i >= num_children_dom);
+			child->AddToStackingContext(stacking_children, is_flex_container, is_non_dom_element);
+		}
 	}
 }
 
@@ -2438,7 +2299,7 @@ void Element::AddToStackingContext(Vector<StackingContextChild>& stacking_childr
 
 	stacking_children.push_back(StackingContextChild{this, order});
 
-	if (include_children && !children.empty())
+	if (include_children && GetNumChildNodes(true) > 0)
 	{
 		const size_t index_child_begin = stacking_children.size();
 
@@ -2469,7 +2330,7 @@ void Element::DirtyDefinition(DirtyNodes dirty_nodes)
 	case DirtyNodes::Self: dirty_definition = true; break;
 	case DirtyNodes::SelfAndSiblings:
 		dirty_definition = true;
-		if (parent)
+		if (Element* parent = GetParentElement())
 			parent->dirty_child_definitions = true;
 		break;
 	}
@@ -2492,7 +2353,7 @@ void Element::UpdateDefinition()
 	if (dirty_child_definitions)
 	{
 		dirty_child_definitions = false;
-		for (const ElementPtr& child : children)
+		for (Element* child : IterateChildren<Element>(true))
 			child->dirty_definition = true;
 	}
 }
@@ -2932,7 +2793,7 @@ void Element::UpdateTransformState()
 			// believe the motivation is. Then we would need to subtract the absolute zero-offsets during geometry submit whenever we have transforms.
 		}
 
-		if (parent && parent->transform_state)
+		if (Element* parent = GetParentElement(); parent && parent->transform_state)
 		{
 			// Apply the parent's local perspective and transform.
 			// @performance: If we have no local transform and no parent perspective, we can effectively just point to the parent transform instead of
@@ -2968,8 +2829,8 @@ void Element::UpdateTransformState()
 	// A change in perspective or transform will require an update to children transforms as well.
 	if (perspective_or_transform_changed)
 	{
-		for (size_t i = 0; i < children.size(); i++)
-			children[i]->DirtyTransformState(false, true);
+		for (Element* child : IterateChildren<Element>(true))
+			child->DirtyTransformState(false, true);
 	}
 
 	// No reason to keep the transform state around if transform and perspective have been removed.

+ 278 - 0
Source/Core/Node.cpp

@@ -0,0 +1,278 @@
+/*
+ * 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-2025 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 "../../Include/RmlUi/Core/Node.h"
+#include "../../Include/RmlUi/Core/Context.h"
+#include "../../Include/RmlUi/Core/Element.h"
+#include "../../Include/RmlUi/Core/ElementDocument.h"
+#include <iterator>
+
+namespace Rml {
+
+Node::Node() {}
+
+Node::~Node()
+{
+	RMLUI_ASSERT(parent == nullptr);
+
+	// A simplified version of RemoveChild() for destruction.
+	for (NodePtr& child : children)
+		child->SetParent(nullptr);
+
+	children.clear();
+	num_non_dom_children = 0;
+}
+
+Node* Node::GetChildNode(int index) const
+{
+	if (index < 0 || index >= (int)children.size())
+		return nullptr;
+
+	return children[index].get();
+}
+
+int Node::GetNumChildNodes(bool include_non_dom_elements) const
+{
+	return (int)children.size() - (include_non_dom_elements ? 0 : num_non_dom_children);
+}
+
+bool Node::HasChildNodes() const
+{
+	return (int)children.size() > num_non_dom_children;
+}
+
+Node* Node::AppendChild(NodePtr child, bool dom_node)
+{
+	RMLUI_ASSERT(child && child.get() != this);
+	Node* child_ptr = child.get();
+	if (dom_node)
+	{
+		auto it_end = children.end();
+		children.insert(it_end - num_non_dom_children, std::move(child));
+	}
+	else
+	{
+		children.push_back(std::move(child));
+		num_non_dom_children++;
+	}
+	// Set parent just after inserting into children. This allows us to e.g. get our previous sibling in SetParent.
+	child_ptr->SetParent(this);
+
+	OnChildNodeAdd(child_ptr, dom_node);
+
+	return child_ptr;
+}
+
+Node* Node::InsertBefore(NodePtr child, Node* adjacent_node)
+{
+	RMLUI_ASSERT(child);
+	// Find the position in the list of children of the adjacent element. If it's nullptr, or we can't find it, then we
+	// insert it at the end of the dom children, as a dom element.
+	size_t child_index = 0;
+	bool found_child = false;
+	if (adjacent_node)
+	{
+		for (child_index = 0; child_index < children.size(); child_index++)
+		{
+			if (children[child_index].get() == adjacent_node)
+			{
+				found_child = true;
+				break;
+			}
+		}
+	}
+
+	Node* child_ptr = nullptr;
+
+	if (found_child)
+	{
+		child_ptr = child.get();
+
+		const bool dom_node = ((int)child_index < GetNumChildNodes());
+		if (!dom_node)
+			num_non_dom_children++;
+
+		children.insert(children.begin() + child_index, std::move(child));
+		child_ptr->SetParent(this);
+
+		OnChildNodeAdd(child_ptr, dom_node);
+	}
+	else
+	{
+		child_ptr = AppendChild(std::move(child));
+	}
+
+	return child_ptr;
+}
+
+NodePtr Node::ReplaceChild(NodePtr insert_node, Node* replace_node)
+{
+	RMLUI_ASSERT(insert_node);
+	auto it_replace = std::find_if(children.begin(), children.end(), [replace_node](const NodePtr& node) { return node.get() == replace_node; });
+	if (it_replace == children.end())
+	{
+		AppendChild(std::move(insert_node));
+		return nullptr;
+	}
+
+	const std::ptrdiff_t replace_index = std::distance(children.begin(), it_replace);
+	const bool dom_node = (replace_index < (std::ptrdiff_t)children.size() - num_non_dom_children);
+
+	Node* inserted_node_ptr = insert_node.get();
+	children.insert(children.begin() + replace_index, std::move(insert_node));
+	if (!dom_node)
+		num_non_dom_children++;
+	inserted_node_ptr->SetParent(this);
+
+	NodePtr result = RemoveChild(replace_node);
+
+	OnChildNodeAdd(inserted_node_ptr, dom_node);
+
+	return result;
+}
+
+NodePtr Node::RemoveChild(Node* child)
+{
+	auto it_child = std::find_if(children.begin(), children.end(), [child](const NodePtr& node) { return node.get() == child; });
+	if (it_child == children.end())
+		return nullptr;
+
+	const std::ptrdiff_t child_index = std::distance(children.begin(), it_child);
+	const bool dom_node = (child_index < (std::ptrdiff_t)children.size() - num_non_dom_children);
+	OnChildNodeRemove(child, dom_node);
+
+	if (!dom_node)
+		num_non_dom_children--;
+
+	NodePtr detached_child = std::move(*it_child);
+	children.erase(it_child);
+
+	detached_child->SetParent(nullptr);
+
+	return detached_child;
+}
+
+Element* Node::AppendChild(ElementPtr child, bool dom_element)
+{
+	return rmlui_static_cast<Element*>(AppendChild(As<NodePtr>(std::move(child)), dom_element));
+}
+
+Element* Node::InsertBefore(ElementPtr child, Element* adjacent_element)
+{
+	return rmlui_static_cast<Element*>(InsertBefore(As<NodePtr>(std::move(child)), adjacent_element));
+}
+
+ElementPtr Node::ReplaceChild(ElementPtr inserted_element, Element* replaced_element)
+{
+	return As<ElementPtr>(ReplaceChild(As<NodePtr>(std::move(inserted_element)), replaced_element));
+}
+
+ElementPtr Node::RemoveChild(Element* child)
+{
+	return As<ElementPtr>(RemoveChild(As<Node*>(child)));
+}
+
+Element* Node::GetParentNode() const
+{
+	return GetParentElement();
+}
+
+Element* Node::GetParentElement() const
+{
+	return rmlui_dynamic_cast<Element*>(parent);
+}
+
+ElementDocument* Node::GetOwnerDocument() const
+{
+#ifdef RMLUI_DEBUG
+	if (parent && !owner_document)
+	{
+		// Since we have a parent but no owner_document, then we must be a 'loose' element -- that is, constructed
+		// outside of a document and not attached to a child of any element in the hierarchy of a document.
+		// This check ensures that we didn't just forget to set the owner document.
+		RMLUI_ASSERT(!parent->GetOwnerDocument());
+	}
+#endif
+
+	return owner_document;
+}
+
+Context* Node::GetContext() const
+{
+	ElementDocument* document = GetOwnerDocument();
+	if (document != nullptr)
+		return document->GetContext();
+
+	return nullptr;
+}
+
+RenderManager* Node::GetRenderManager() const
+{
+	if (Context* context = GetContext())
+		return &context->GetRenderManager();
+	return nullptr;
+}
+
+void Node::SetOwnerDocument(ElementDocument* document)
+{
+	if (owner_document && !document)
+	{
+		// We are detaching from the document and thereby also the context.
+		if (Context* context = owner_document->GetContext())
+		{
+			if (Element* self = AsIf<Element*>(this))
+				context->OnElementDetach(self);
+		}
+	}
+
+	// If this element is a document, then never change owner_document.
+	if (owner_document != this && owner_document != document)
+	{
+		owner_document = document;
+		for (NodePtr& child : children)
+			child->SetOwnerDocument(document);
+	}
+}
+
+void Node::OnChildNodeAdd(Node* /*child*/, bool /*dom_node*/) {}
+void Node::OnChildNodeRemove(Node* /*child*/, bool /*dom_node*/) {}
+void Node::OnParentChange(Node* /*parent*/) {}
+
+void Node::SetParent(Node* new_parent)
+{
+	// Assumes we are either attaching to or detaching from the hierarchy.
+	RMLUI_ASSERT((parent == nullptr) != (new_parent == nullptr));
+
+	parent = new_parent;
+
+	SetOwnerDocument(parent ? parent->GetOwnerDocument() : nullptr);
+
+	OnParentChange(parent);
+}
+
+} // namespace Rml