Browse Source

Refactor ElementUtilities to make breadth-first search reusable

Refactor the radio button group lookup to use the new utility.

# Conflicts:
#	Include/RmlUi/Core/ElementUtilities.h
#	Source/Core/ElementUtilities.cpp
Michael Ragazzon 4 months ago
parent
commit
9b8cdfcb64

+ 36 - 2
Include/RmlUi/Core/ElementUtilities.h

@@ -29,6 +29,7 @@
 #ifndef RMLUI_CORE_ELEMENTUTILITIES_H
 #define RMLUI_CORE_ELEMENTUTILITIES_H
 
+#include "Element.h"
 #include "Header.h"
 #include "RenderManager.h"
 #include "Types.h"
@@ -69,8 +70,7 @@ public:
 	/// @param[out] elements Resulting elements.
 	/// @param[in] root_element First element to check.
 	/// @param[in] tag Tag to search for.
-	/// @param[in] stop_tag Optional, tag to stop searching at i.e. won't look for other elements within this tag.
-	static void GetElementsByTagName(ElementList& elements, Element* root_element, const String& tag, const String& stop_tag = "");
+	static void GetElementsByTagName(ElementList& elements, Element* root_element, const String& tag);
 	/// Get all elements with the given class set on them.
 	/// @param[out] elements Resulting elements.
 	/// @param[in] root_element First element to check.
@@ -138,6 +138,40 @@ public:
 	/// Attributes such as 'data-' are used to create the views and controllers.
 	/// @return True if a data view or controller was constructed.
 	static bool ApplyDataViewsControllers(Element* element);
+
+	enum class CallbackControlFlow { Continue, SkipChildren, Break };
+
+	template <typename Func>
+	static void BreadthFirstSearch(Element* root_element, Func&& func)
+	{
+		// Breadth-first search over the element tree, break when 'func' returns true.
+		Queue<Element*> search_queue;
+		search_queue.push(root_element);
+
+		while (!search_queue.empty())
+		{
+			Element* element = search_queue.front();
+			search_queue.pop();
+
+			if constexpr (std::is_same_v<std::invoke_result_t<Func, Element*>, CallbackControlFlow>)
+			{
+				const CallbackControlFlow control_flow = func(element);
+				if (control_flow == CallbackControlFlow::Break)
+					break;
+				if (control_flow == CallbackControlFlow::SkipChildren)
+					continue;
+			}
+			else
+			{
+				func(element);
+			}
+
+			// Add all children to search
+			const int num_children = element->GetNumChildren();
+			for (int i = 0; i < num_children; i++)
+				search_queue.push(element->GetChild(i));
+		}
+	}
 };
 
 } // namespace Rml

+ 12 - 54
Source/Core/ElementUtilities.cpp

@@ -51,74 +51,32 @@ namespace Rml {
 
 Element* ElementUtilities::GetElementById(Element* root_element, const String& id)
 {
-	// Breadth first search on elements for the corresponding id
-	typedef Queue<Element*> SearchQueue;
-	SearchQueue search_queue;
-	search_queue.push(root_element);
-
-	while (!search_queue.empty())
-	{
-		Element* element = search_queue.front();
-		search_queue.pop();
-
+	Element* result = nullptr;
+	BreadthFirstSearch(root_element, [&](Element* element) {
 		if (element->GetId() == id)
 		{
-			return element;
+			result = element;
+			return CallbackControlFlow::Break;
 		}
-
-		// Add all children to search
-		for (int i = 0; i < element->GetNumChildren(); i++)
-			search_queue.push(element->GetChild(i));
-	}
-
-	return nullptr;
+		return CallbackControlFlow::Continue;
+	});
+	return result;
 }
 
-void ElementUtilities::GetElementsByTagName(ElementList& elements, Element* root_element, const String& tag, const String& stop_tag)
+void ElementUtilities::GetElementsByTagName(ElementList& elements, Element* root_element, const String& tag)
 {
-	// Breadth first search on elements for the corresponding id
-	typedef Queue<Element*> SearchQueue;
-	SearchQueue search_queue;
-	for (int i = 0; i < root_element->GetNumChildren(); ++i)
-		search_queue.push(root_element->GetChild(i));
-
-	while (!search_queue.empty())
-	{
-		Element* element = search_queue.front();
-		search_queue.pop();
-
+	BreadthFirstSearch(root_element, [&](Element* element) {
 		if (element->GetTagName() == tag)
 			elements.push_back(element);
-
-		if (stop_tag.empty() || element->GetTagName() != stop_tag)
-		{
-			// Add all children to search.
-			for (int i = 0; i < element->GetNumChildren(); i++)
-				search_queue.push(element->GetChild(i));
-		}
-	}
+	});
 }
 
 void ElementUtilities::GetElementsByClassName(ElementList& elements, Element* root_element, const String& class_name)
 {
-	// Breadth first search on elements for the corresponding id
-	typedef Queue<Element*> SearchQueue;
-	SearchQueue search_queue;
-	for (int i = 0; i < root_element->GetNumChildren(); ++i)
-		search_queue.push(root_element->GetChild(i));
-
-	while (!search_queue.empty())
-	{
-		Element* element = search_queue.front();
-		search_queue.pop();
-
+	BreadthFirstSearch(root_element, [&](Element* element) {
 		if (element->IsClassSet(class_name))
 			elements.push_back(element);
-
-		// Add all children to search.
-		for (int i = 0; i < element->GetNumChildren(); i++)
-			search_queue.push(element->GetChild(i));
-	}
+	});
 }
 
 float ElementUtilities::GetDensityIndependentPixelRatio(Element* element)

+ 14 - 14
Source/Core/Elements/InputTypeRadio.cpp

@@ -97,31 +97,31 @@ void InputTypeRadio::PopRadioSet()
 	// Uncheck all other radio buttons with our name in the form.
 	String stop_tag;
 	Element* parent = element->GetParentNode();
-	while (parent != nullptr && rmlui_dynamic_cast<ElementForm*>(parent) == nullptr)
+	while (parent && rmlui_dynamic_cast<ElementForm*>(parent) == nullptr)
 		parent = parent->GetParentNode();
 
-	//If no containing form was found, use the containing document as the parent
-	if (parent == nullptr)
-	{
+	// If no containing form was found, use the containing document as the parent.
+	if (!parent)
 		parent = element->GetOwnerDocument();
-		stop_tag = "form"; // Don't include any radios that are inside form elements
-	}
 
-	if (parent != nullptr)
-	{
-		ElementList form_controls;
-		ElementUtilities::GetElementsByTagName(form_controls, parent, "input", stop_tag);
+	if (!parent)
+		return;
 
-		for (size_t i = 0; i < form_controls.size(); ++i)
+	ElementUtilities::BreadthFirstSearch(parent, [&](Element* candidate) {
+		// Don't include any radios that are inside other form elements.
+		if (candidate != parent && rmlui_dynamic_cast<ElementForm*>(candidate) != nullptr)
+			return ElementUtilities::CallbackControlFlow::SkipChildren;
+
+		if (ElementFormControlInput* radio_control = rmlui_dynamic_cast<ElementFormControlInput*>(candidate))
 		{
-			ElementFormControlInput* radio_control = rmlui_dynamic_cast<ElementFormControlInput*>(form_controls[i]);
-			if (radio_control != nullptr && element != radio_control && radio_control->GetAttribute<String>("type", "text") == "radio" &&
+			if (element != radio_control && radio_control->GetAttribute<String>("type", "text") == "radio" &&
 				radio_control->GetName() == element->GetName())
 			{
 				radio_control->RemoveAttribute("checked");
 			}
 		}
-	}
+		return ElementUtilities::CallbackControlFlow::Continue;
+	});
 }
 
 } // namespace Rml