Browse Source

Merge branch 'observer_ptr'

Michael Ragazzon 6 years ago
parent
commit
d1c7a2f741

+ 2 - 0
CMake/FileList.cmake

@@ -138,6 +138,7 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/MathTypes.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.inl
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ObserverPtr.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Platform.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Plugin.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Profiling.h
@@ -246,6 +247,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutLineBox.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Log.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Math.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/ObserverPtr.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Plugin.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/precompiled.cpp

+ 2 - 1
Include/RmlUi/Core/Element.h

@@ -34,6 +34,7 @@
 #include "Box.h"
 #include "ComputedValues.h"
 #include "Event.h"
+#include "ObserverPtr.h"
 #include "Property.h"
 #include "Types.h"
 #include "Transform.h"
@@ -70,7 +71,7 @@ struct ElementMeta;
 	@author Peter Curry
  */
 
-class RMLUICORE_API Element : public ScriptInterface
+class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr<Element>
 {
 public:
 	/// Constructs a new RmlUi element. This should not be called directly; use the Factory

+ 2 - 1
Include/RmlUi/Core/EventListener.h

@@ -31,6 +31,7 @@
 
 #include "Header.h"
 #include "Event.h"
+#include "ObserverPtr.h"
 
 namespace Rml {
 namespace Core {
@@ -44,7 +45,7 @@ class Element;
 	@author Lloyd Weehuizen
  */
 
-class RMLUICORE_API EventListener
+class RMLUICORE_API EventListener : public EnableObserverPtr<EventListener>
 {
 public:
 	virtual ~EventListener() {}

+ 191 - 0
Include/RmlUi/Core/ObserverPtr.h

@@ -0,0 +1,191 @@
+/*
+ * 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 RMLUIOBSERVERPTR_H
+#define RMLUIOBSERVERPTR_H
+
+#include <utility>
+#include <type_traits>
+#include "Header.h"
+
+namespace Rml {
+namespace Core {
+
+struct RMLUICORE_API ObserverPtrBlock {
+	int num_observers;
+	void* pointed_to_object;
+};
+RMLUICORE_API ObserverPtrBlock* AllocateObserverPtrBlock();
+RMLUICORE_API void DeallocateObserverPtrBlockIfEmpty(ObserverPtrBlock* block);
+
+template<typename T>
+class EnableObserverPtr;
+
+
+/**
+	Observer pointer.
+
+	Holds a weak reference to an object owned by someone else. Can tell whether or not the pointed to object has been destroyed.
+
+	Usage: Given a class T, derive from EnableObserverPtr<T>. Then, we can use the observer pointer as follows:
+
+		auto object = std::make_unique<T>();
+		ObserverPtr<T> observer_ptr = object->GetObserverPtr();
+		// ...
+		if(obserer_ptr) { 
+			// Will only enter if object is still alive.
+			observer_ptr->do_a_thing(); 
+		} 
+
+	Note: Not thread safe.
+ */
+
+template<typename T>
+class RMLUICORE_API ObserverPtr {
+public:
+	ObserverPtr() noexcept : block(nullptr) {}
+	ObserverPtr(std::nullptr_t) noexcept : block(nullptr) {}
+	~ObserverPtr() noexcept {
+		reset(); 
+	}
+
+	// Copy
+	ObserverPtr<T>(const ObserverPtr<T>& other) noexcept : block(other.block) {
+		if (block)
+			block->num_observers += 1;
+	}
+	ObserverPtr<T>& operator=(const ObserverPtr<T>& other) noexcept {
+		reset();
+		block = other.block;
+		if (block)
+			block->num_observers += 1;
+		return *this;
+	}
+
+	// Move
+	ObserverPtr<T>(ObserverPtr<T>&& other) noexcept : block(std::exchange(other.block, nullptr)) {}
+	ObserverPtr<T>& operator=(ObserverPtr<T>&& other) noexcept {
+		reset();
+		block = std::exchange(other.block, nullptr);
+		return *this;
+	}
+
+	// Returns true if we can dereference the pointer.
+	explicit operator bool() const noexcept { return block && block->pointed_to_object; }
+
+	// Retrieve the pointer to the observed object if we have one and it's still alive.
+	T* get() noexcept {
+		return block ? static_cast<T*>(block->pointed_to_object) : nullptr;
+	}
+	// Dereference the pointed to object.
+	T* operator->() noexcept { return static_cast<T*>(block->pointed_to_object); }
+
+	// Reset the pointer so that it does not point to anything.
+	// When the pointed to object and all observer pointers to it have been destroyed, it will deallocate the block.
+	void reset() noexcept
+	{
+		if (block)
+		{
+			block->num_observers -= 1;
+			DeallocateObserverPtrBlockIfEmpty(block);
+			block = nullptr;
+		}
+	}
+
+private:
+
+	friend class EnableObserverPtr<T>;
+
+	explicit ObserverPtr(ObserverPtrBlock* block) noexcept : block(block)
+	{
+		if (block)
+			block->num_observers += 1;
+	}
+
+	ObserverPtrBlock* block;
+};
+
+
+
+
+template<typename T>
+class RMLUICORE_API EnableObserverPtr {
+public:
+
+	ObserverPtr<T> GetObserverPtr() const noexcept { return ObserverPtr<T>(block); }
+
+protected:
+
+	EnableObserverPtr() noexcept
+	{
+		static_assert(std::is_base_of<EnableObserverPtr<T>, T>::value, "T must derive from EnableObserverPtr<T>.");
+		InitializeBlock();
+	}
+
+	~EnableObserverPtr() noexcept 
+	{
+		block->pointed_to_object = nullptr;
+		DeallocateObserverPtrBlockIfEmpty(block);
+	}
+
+	EnableObserverPtr<T>(const EnableObserverPtr<T>&) noexcept {
+		// We do not copy or modify the block, it should always point to the same object.
+		InitializeBlock();
+	}
+	EnableObserverPtr<T>& operator=(const EnableObserverPtr<T>&) noexcept { 
+		// Assignment should not do anything, the block must point to the initially constructed object.
+		return *this; 
+	}
+
+	EnableObserverPtr<T>(EnableObserverPtr<T>&&) noexcept {
+		// We do not move or modify the block, it should always point to the same object.
+		InitializeBlock();
+	}
+	EnableObserverPtr<T>& operator=(EnableObserverPtr<T>&&) noexcept {
+		// Assignment should not do anything, the block must point to the initially constructed object.
+		return *this;
+	}
+
+private:
+
+	inline void InitializeBlock() noexcept
+	{
+		block = AllocateObserverPtrBlock();
+		block->num_observers = 0;
+		block->pointed_to_object = static_cast<void*>(static_cast<T*>(this));
+	}
+
+	ObserverPtrBlock* block;
+};
+
+
+
+}
+}
+
+#endif

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

@@ -73,6 +73,7 @@ enum class Character : char32_t { Null, Replacement = 0xfffd };
 #include "Vector3.h"
 #include "Vector4.h"
 #include "Matrix4.h"
+#include "ObserverPtr.h"
 
 namespace Rml {
 namespace Core {
@@ -130,6 +131,8 @@ template<typename T>
 using UniqueReleaserPtr = std::unique_ptr<T, Releaser<T>>;
 template<typename T>
 using SharedPtr = std::shared_ptr<T>;
+template<typename T>
+using WeakPtr = std::weak_ptr<T>;
 
 using ElementPtr = UniqueReleaserPtr<Element>;
 using ContextPtr = UniqueReleaserPtr<Context>;

+ 2 - 2
Samples/basic/demo/data/demo.rml

@@ -171,7 +171,7 @@ textarea {
 </panel>
 <tab>Decorators</tab>
 <panel id="decorators">
-	<div>	
+	<div>
 		<button>Image</button>
 		<button class="ninepatch">Ninepatch</button>
 	</div>
@@ -220,7 +220,7 @@ textarea {
 <tab>Buttons</tab>
 <panel>
 	<button id="start_game">Start Game</button><br />
-	<button id="high_scores" onkeydown="hello" autofocus>High Scores</button><br />
+	<button id="high_scores" autofocus>High Scores</button><br />
 	<button id="options">Options</button><br />
 	<button id="help">Help</button><br />
 	<div><button id="exit" onclick="exit">Exit</button></div>

+ 28 - 37
Samples/basic/demo/src/main.cpp

@@ -36,12 +36,6 @@
 
 #include <sstream>
 
-bool run_loop = true;
-bool single_loop = false;
-
-class DemoWindow;
-DemoWindow* window = nullptr;
-
 class DemoWindow : public Rml::Core::EventListener
 {
 public:
@@ -78,17 +72,9 @@ public:
 		case EventId::Keydown:
 		{
 			Rml::Core::Input::KeyIdentifier key_identifier = (Rml::Core::Input::KeyIdentifier) event.GetParameter< int >("key_identifier", 0);
+			bool ctrl_key = event.GetParameter< bool >("ctrl_key", false);
 
-			if (key_identifier == Rml::Core::Input::KI_SPACE)
-			{
-				run_loop = !run_loop;
-			}
-			else if (key_identifier == Rml::Core::Input::KI_RETURN)
-			{
-				run_loop = false;
-				single_loop = true;
-			}
-			else if (key_identifier == Rml::Core::Input::KI_ESCAPE)
+			if (key_identifier == Rml::Core::Input::KI_ESCAPE)
 			{
 				Shell::RequestExit();
 			}
@@ -119,25 +105,20 @@ ShellRenderInterfaceExtensions *shell_renderer;
 
 void GameLoop()
 {
-	if(run_loop || single_loop)
-	{
-		context->Update();
-
-		shell_renderer->PrepareRenderBuffer();
-		context->Render();
-		shell_renderer->PresentRenderBuffer();
+	context->Update();
 
-		single_loop = false;
-	}
+	shell_renderer->PrepareRenderBuffer();
+	context->Render();
+	shell_renderer->PresentRenderBuffer();
 }
 
 
 
 
-class Event : public Rml::Core::EventListener
+class DemoEventListener : public Rml::Core::EventListener
 {
 public:
-	Event(const Rml::Core::String& value, Rml::Core::Element* element) : value(value), element(element) {}
+	DemoEventListener(const Rml::Core::String& value, Rml::Core::Element* element) : value(value), element(element) {}
 
 	void ProcessEvent(Rml::Core::Event& event) override
 	{
@@ -145,13 +126,23 @@ public:
 
 		if (value == "exit")
 		{
-			element->GetParentNode()->SetInnerRML("<button onclick='confirm_exit'>Are you sure?</button>");
-			event.StopImmediatePropagation();
+			// Test replacing the current element.
+			// Need to be careful with regard to lifetime issues. The event's current element will be destroyed, so we cannot
+			// use it after SetInnerRml(). The library should handle this case safely internally when propagating the event further.
+			auto parent = element->GetParentNode();
+			parent->SetInnerRML("<button onclick='confirm_exit' onblur='cancel_exit' onmouseout='cancel_exit'>Are you sure?</button>");
+			if (auto child = parent->GetChild(0))
+				child->Focus();
 		}
 		else if (value == "confirm_exit")
 		{
 			Shell::RequestExit();
 		}
+		else if (value == "cancel_exit")
+		{
+			if(auto parent = element->GetParentNode())
+				parent->SetInnerRML("<button id='exit' onclick='exit'>Exit</button>");
+		}
 	}
 
 	void OnDetach(Rml::Core::Element* element) override { delete this; }
@@ -163,12 +154,12 @@ private:
 
 
 
-class EventInstancer : public Rml::Core::EventListenerInstancer
+class DemoEventListenerInstancer : public Rml::Core::EventListenerInstancer
 {
 public:
 	Rml::Core::EventListener* InstanceEventListener(const Rml::Core::String& value, Rml::Core::Element* element) override
 	{
-		return new Event(value, element);
+		return new DemoEventListener(value, element);
 	}
 };
 
@@ -229,15 +220,15 @@ int main(int RMLUI_UNUSED_PARAMETER(argc), char** RMLUI_UNUSED_PARAMETER(argv))
 	
 	context->SetDensityIndependentPixelRatio(1.0f);
 
-	EventInstancer event_listener_instancer;
+	DemoEventListenerInstancer event_listener_instancer;
 	Rml::Core::Factory::RegisterEventListenerInstancer(&event_listener_instancer);
 
 	Shell::LoadFonts("assets/");
 
-	window = new DemoWindow("Demo sample", Rml::Core::Vector2f(150, 80), context);
-	window->GetDocument()->AddEventListener(Rml::Core::EventId::Keydown, window);
-	window->GetDocument()->AddEventListener(Rml::Core::EventId::Keyup, window);
-	window->GetDocument()->AddEventListener(Rml::Core::EventId::Animationend, window);
+	auto window = std::make_unique<DemoWindow>("Demo sample", Rml::Core::Vector2f(150, 80), context);
+	window->GetDocument()->AddEventListener(Rml::Core::EventId::Keydown, window.get());
+	window->GetDocument()->AddEventListener(Rml::Core::EventId::Keyup, window.get());
+	window->GetDocument()->AddEventListener(Rml::Core::EventId::Animationend, window.get());
 
 	Shell::EventLoop(GameLoop);
 
@@ -249,7 +240,7 @@ int main(int RMLUI_UNUSED_PARAMETER(argc), char** RMLUI_UNUSED_PARAMETER(argv))
 	Shell::CloseWindow();
 	Shell::Shutdown();
 
-	delete window;
+	window.reset();
 
 	return 0;
 }

+ 39 - 12
Source/Core/Context.cpp

@@ -615,13 +615,13 @@ void Context::ProcessMouseButtonDown(int button_index, int key_modifier_state)
 		// Save the just-pressed-on element as the pressed element.
 		active = new_focus;
 
-		bool propogate = true;
+		bool propagate = true;
 		
 		// Call 'onmousedown' on every item in the hover chain, and copy the hover chain to the active chain.
 		if (hover)
-			propogate = hover->DispatchEvent(EventId::Mousedown, parameters);
+			propagate = hover->DispatchEvent(EventId::Mousedown, parameters);
 
-		if (propogate)
+		if (propagate)
 		{
 			// Check for a double-click on an element; if one has occured, we send the 'dblclick' event to the hover
 			// element. If not, we'll start a timer to catch the next one.
@@ -630,7 +630,7 @@ void Context::ProcessMouseButtonDown(int button_index, int key_modifier_state)
 				float(click_time - last_click_time) < DOUBLE_CLICK_TIME)
 			{
 				if (hover)
-					propogate = hover->DispatchEvent(EventId::Dblclick, parameters);
+					propagate = hover->DispatchEvent(EventId::Dblclick, parameters);
 
 				last_click_element = nullptr;
 				last_click_time = 0;
@@ -646,7 +646,7 @@ void Context::ProcessMouseButtonDown(int button_index, int key_modifier_state)
 		for (ElementSet::iterator itr = hover_chain.begin(); itr != hover_chain.end(); ++itr)
 			active_chain.push_back((*itr));
 
-		if (propogate)
+		if (propagate)
 		{
 			// Traverse down the hierarchy of the newly focussed element (if any), and see if we can begin dragging it.
 			drag_started = false;
@@ -797,8 +797,7 @@ void Context::OnElementDetach(Element* element)
 	{
 		Dictionary parameters;
 		GenerateMouseEventParameters(parameters, -1);
-		RmlEventFunctor send_event(EventId::Mouseout, parameters);
-		send_event(element);
+		element->DispatchEvent(EventId::Mouseout, parameters);
 
 		hover_chain.erase(it_hover);
 
@@ -1160,7 +1159,7 @@ void Context::GenerateMouseEventParameters(Dictionary& parameters, int button_in
 // Builds the parameters for the key modifier state.
 void Context::GenerateKeyModifierEventParameters(Dictionary& parameters, int key_modifier_state)
 {
-	static String property_names[] = {
+	static const String property_names[] = {
 		"ctrl_key",
 		"shift_key",
 		"alt_key",
@@ -1195,13 +1194,41 @@ void Context::ReleaseUnloadedDocuments()
 	}
 }
 
+using ElementObserverList = std::vector< ObserverPtr<Element> >;
+
+class ElementObserverListBackInserter {
+public:
+	using iterator_category = std::output_iterator_tag;
+	using value_type = void;
+	using difference_type = void;
+	using pointer = void;
+	using reference = void;
+	using container_type = ElementObserverList;
+
+	ElementObserverListBackInserter(ElementObserverList& elements) : elements(&elements) {}
+	ElementObserverListBackInserter& operator=(const Element* element) {
+		elements->push_back(element->GetObserverPtr());
+		return *this;
+	}
+	ElementObserverListBackInserter& operator*() { return *this; }
+	ElementObserverListBackInserter& operator++() { return *this; }
+	ElementObserverListBackInserter& operator++(int) { return *this; }
+
+private:
+	ElementObserverList* elements;
+};
+
 // Sends the specified event to all elements in new_items that don't appear in old_items.
 void Context::SendEvents(const ElementSet& old_items, const ElementSet& new_items, EventId id, const Dictionary& parameters)
 {
-	ElementList elements;
-	std::set_difference(old_items.begin(), old_items.end(), new_items.begin(), new_items.end(), std::back_inserter(elements));
-	RmlEventFunctor func(id, parameters);
-	std::for_each(elements.begin(), elements.end(), func);
+	// We put our elements in observer pointers in case some of them are deleted during dispatch.
+	ElementObserverList elements;
+	std::set_difference(old_items.begin(), old_items.end(), new_items.begin(), new_items.end(), ElementObserverListBackInserter(elements));
+	for (auto& element : elements)
+	{
+		if (element)
+			element->DispatchEvent(id, parameters);
+	}
 }
 
 void Context::Release()

+ 5 - 4
Source/Core/Element.cpp

@@ -105,7 +105,8 @@ static Pool< ElementMeta > element_meta_chunk_pool(200, true);
 
 /// Constructs a new RmlUi element.
 Element::Element(const String& tag) : tag(tag), relative_offset_base(0, 0), relative_offset_position(0, 0), absolute_offset(0, 0), scroll_offset(0, 0), content_offset(0, 0), content_box(0, 0), 
-transform_state(), dirty_transform(false), dirty_perspective(false), dirty_animation(false), dirty_transition(false)
+transform_state(), dirty_transform(false), dirty_perspective(false), dirty_animation(false), dirty_transition(false),
+EnableObserverPtr<Element>()
 {
 	RMLUI_ASSERT(tag == StringUtilities::ToLower(tag));
 	parent = nullptr;
@@ -1207,21 +1208,21 @@ void Element::RemoveEventListener(EventId id, EventListener* listener, bool in_c
 bool Element::DispatchEvent(const String& type, const Dictionary& parameters)
 {
 	const EventSpecification& specification = EventSpecificationInterface::GetOrInsert(type);
-	return event_dispatcher->DispatchEvent(this, specification.id, type, parameters, specification.interruptible, specification.bubbles, specification.default_action_phase);
+	return EventDispatcher::DispatchEvent(this, specification.id, type, parameters, specification.interruptible, specification.bubbles, specification.default_action_phase);
 }
 
 // Dispatches the specified event
 bool Element::DispatchEvent(const String& type, const Dictionary& parameters, bool interruptible, bool bubbles)
 {
 	const EventSpecification& specification = EventSpecificationInterface::GetOrInsert(type);
-	return event_dispatcher->DispatchEvent(this, specification.id, type, parameters, interruptible, bubbles, specification.default_action_phase);
+	return EventDispatcher::DispatchEvent(this, specification.id, type, parameters, interruptible, bubbles, specification.default_action_phase);
 }
 
 // Dispatches the specified event
 bool Element::DispatchEvent(EventId id, const Dictionary& parameters)
 {
 	const EventSpecification& specification = EventSpecificationInterface::Get(id);
-	return event_dispatcher->DispatchEvent(this, specification.id, specification.type, parameters, specification.interruptible, specification.bubbles, specification.default_action_phase);
+	return EventDispatcher::DispatchEvent(this, specification.id, specification.type, parameters, specification.interruptible, specification.bubbles, specification.default_action_phase);
 }
 
 // Scrolls the parent element's contents so that this element is visible.

+ 1 - 1
Source/Core/ElementUtilities.cpp

@@ -193,7 +193,7 @@ bool ElementUtilities::GetClippingRegion(Vector2i& clip_origin, Vector2i& clip_d
 			// Ignore nodes that don't clip.
 			if (clipping_element->GetClientWidth() < clipping_element->GetScrollWidth()
 				|| clipping_element->GetClientHeight() < clipping_element->GetScrollHeight())
-			{				
+			{
 				Vector2f element_origin_f = clipping_element->GetAbsoluteOffset(Box::CONTENT);
 				Vector2f element_dimensions_f = clipping_element->GetBox().GetSize(Box::CONTENT);
 				

+ 125 - 66
Source/Core/EventDispatcher.cpp

@@ -108,51 +108,107 @@ void EventDispatcher::DetachAllEvents()
 		element->GetChild(i)->GetEventDispatcher()->DetachAllEvents();
 }
 
-bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const String& type, const Dictionary& parameters, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase)
-{
-	EventPtr event = Factory::InstanceEvent(target_element, id, type, parameters, interruptible);
-	if (!event)
-		return false;
+/*
+	EventListenersToExecute
+
+	When dispatching an event we collect all possible event listeners and default actions to execute.
+	They are stored in observer pointers, so that we can safely check if they have been destroyed since the previous listener execution.
+*/
 
-	// Build the element traversal from the tree
-	typedef std::vector<Element*> Elements;
-	Elements elements;
+struct EventListenersToExecute {
 
-	Element* walk_element = target_element->GetParentNode();
-	while (walk_element) 
+	EventListenersToExecute(Element* _element, EventListener* _listener, int dom_distance_from_target, bool in_capture_phase) : element(_element->GetObserverPtr()), listener(_listener ? _listener->GetObserverPtr() : nullptr)
 	{
-		elements.push_back(walk_element);
-		walk_element = walk_element->GetParentNode();
+		sort = dom_distance_from_target * (in_capture_phase ? -1 : 1);
+		if (!_listener)
+		{
+			// No listener means default action
+			sort += DefaultActionAdd;
+		}
 	}
 
-	event->SetPhase(EventPhase::Capture);
-	// Capture phase - root to target (only triggers event listeners that are registered with capture phase)
-	// Note: We walk elements in REVERSE as they're placed in the list from the elements parent to the root
-	for (int i = (int)elements.size() - 1; i >= 0 && event->IsPropagating(); i--) 
-	{
-		EventDispatcher* dispatcher = elements[i]->GetEventDispatcher();
-		event->SetCurrentElement(elements[i]);
-		dispatcher->TriggerEvents(*event, default_action_phase);
+	static constexpr int DefaultActionMin = 50'000;
+	static constexpr int DefaultActionAdd = 100'000;
+
+	// The sort value is determined by the distance of the element to the target element in the DOM.
+	// Capture phase is given negative values. Default actions add the large number DefaultActionAdd so they are sorted last.
+	int sort = 0;
+
+	ObserverPtr<Element> element;
+	ObserverPtr<EventListener> listener;
+
+	// Default actions are returned by EventPhase::None.
+	EventPhase GetPhase() const { return sort < 0 ? EventPhase::Capture : (sort == 0 ? EventPhase::Target : (sort >= DefaultActionMin ? EventPhase::None : EventPhase::Bubble)); }
+
+	bool operator<(const EventListenersToExecute& other) const {
+		return sort < other.sort;
 	}
+};
 
-	// Target phase - direct at the target
-	if (event->IsPropagating()) 
+
+bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const String& type, const Dictionary& parameters, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase)
+{
+	std::vector<EventListenersToExecute> listeners;
+
+	const EventPhase phases_to_execute = EventPhase((int)EventPhase::Capture | (int)EventPhase::Target | (bubbles ? (int)EventPhase::Bubble : 0));
+	
+	// Walk the DOM tree from target to root, collecting all possible listeners and elements with default actions in the process.
+	int dom_distance_from_target = 0;
+	Element* walk_element = target_element;
+	while (walk_element)
 	{
-		event->SetPhase(EventPhase::Target);
-		event->SetCurrentElement(target_element);
-		TriggerEvents(*event, default_action_phase);
+		EventDispatcher* dispatcher = walk_element->GetEventDispatcher();
+		dispatcher->CollectListeners(dom_distance_from_target, id, phases_to_execute, default_action_phase, listeners);
+
+		walk_element = walk_element->GetParentNode();
+		dom_distance_from_target += 1;
 	}
 
-	// Bubble phase - target to root (normal event bindings)
-	if (bubbles && event->IsPropagating())
+	// Use stable_sort so that the order of the listeners in a given element is maintained.
+	std::stable_sort(listeners.begin(), listeners.end());
+
+	if (listeners.empty())
+		return true;
+
+	// Instance event
+	EventPtr event = Factory::InstanceEvent(target_element, id, type, parameters, interruptible);
+	if (!event)
+		return false;
+
+	int previous_sort_value = INT_MAX;
+	EventPhase current_phase = EventPhase::None;
+
+	// Process the event in each listener.
+	for (auto& listener_desc : listeners)
 	{
-		event->SetPhase(EventPhase::Bubble);
-		for (size_t i = 0; i < elements.size() && event->IsPropagating(); i++) 
+		Element* element = listener_desc.element.get();
+		EventListener* listener = listener_desc.listener.get();
+
+		if (listener_desc.sort != previous_sort_value)
+		{
+			// New sort values represent a new level in the DOM, thus, set the new element and possibly new phase.
+			if (!event->IsPropagating())
+				break;
+			event->SetCurrentElement(element);
+			current_phase = listener_desc.GetPhase();
+			event->SetPhase(current_phase);
+			previous_sort_value = listener_desc.sort;
+		}
+
+		if (element && listener)
 		{
-			EventDispatcher* dispatcher = elements[i]->GetEventDispatcher();
-			event->SetCurrentElement(elements[i]);
-			dispatcher->TriggerEvents(*event, default_action_phase);
+			// We have a valid event listener
+			listener->ProcessEvent(*event);
 		}
+		else if (element && current_phase == EventPhase::None)
+		{
+			// EventPhase::None means default actions. We assume default actions only execute in either target or bubble phase.
+			event->SetPhase(element == target_element ? EventPhase::Target : EventPhase::Bubble);
+			element->ProcessDefaultAction(*event);
+		}
+
+		if (!event->IsImmediatePropagating())
+			break;
 	}
 
 	bool propagating = event->IsPropagating();
@@ -160,6 +216,42 @@ bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const S
 	return propagating;
 }
 
+
+void EventDispatcher::CollectListeners(int dom_distance_from_target, const EventId event_id, const EventPhase event_executes_in_phases, const DefaultActionPhase default_action_phase, std::vector<EventListenersToExecute>& collect_listeners)
+{
+	const bool in_target_phase = (dom_distance_from_target == 0);
+
+	// Find all the entries with a matching id, given that listeners are sorted by id first.
+	Listeners::iterator begin, end;
+	std::tie(begin, end) = std::equal_range(listeners.begin(), listeners.end(), EventListenerEntry(event_id, nullptr, false), CompareId());
+
+	for (auto it = begin; it != end; ++it)
+	{
+		if (in_target_phase)
+		{
+			// Listeners always attach to target phase, but make sure event can actually execute in target phase.
+			if ((int)event_executes_in_phases & (int)EventPhase::Target)
+				collect_listeners.emplace_back(element, it->listener, dom_distance_from_target, it->in_capture_phase);
+		}
+		else
+		{
+			// Listeners will either attach to capture or bubble phase, make sure event can execute in the same phase.
+			const EventPhase listener_executes_in_phase = (it->in_capture_phase ? EventPhase::Capture : EventPhase::Bubble);
+			if ((int)event_executes_in_phases & (int)listener_executes_in_phase)
+				collect_listeners.emplace_back(element, it->listener, dom_distance_from_target, it->in_capture_phase);
+		}
+	}
+
+	RMLUI_ASSERTMSG(!((int)default_action_phase & (int)EventPhase::Capture), "We assume here that the default action phases cannot include capture phase.");
+
+	// Add the default action
+	EventPhase phase_for_default_action = (in_target_phase ? EventPhase::Target : EventPhase::Bubble);
+
+	if ((int)phase_for_default_action & (int)default_action_phase)
+		collect_listeners.emplace_back(element, nullptr, dom_distance_from_target, false);
+}
+
+
 String EventDispatcher::ToString() const
 {
 	String result;
@@ -188,45 +280,12 @@ String EventDispatcher::ToString() const
 	if (count > 0)
 		add_to_result(previous_id, count);
 
-	if (result.size() > 2) 
+	if (result.size() > 2)
 		result.resize(result.size() - 2);
 
 	return result;
 }
 
-void EventDispatcher::TriggerEvents(Event& event, DefaultActionPhase default_action_phase)
-{
-	const EventPhase phase = event.GetPhase();
-
-	// Find the range of entries with matching id and phase, given that listeners are sorted by (id,phase).
-	// In the case of target phase we will match any listener phase.
-	Listeners::iterator begin, end;
-	if (phase == EventPhase::Capture)
-		std::tie(begin, end) = std::equal_range(listeners.begin(), listeners.end(), EventListenerEntry(event.GetId(), nullptr, true), CompareIdPhase());
-	else if (phase == EventPhase::Target)
-		std::tie(begin, end) = std::equal_range(listeners.begin(), listeners.end(), EventListenerEntry(event.GetId(), nullptr, false), CompareId());
-	else if (phase == EventPhase::Bubble)
-		std::tie(begin, end) = std::equal_range(listeners.begin(), listeners.end(), EventListenerEntry(event.GetId(), nullptr, false), CompareIdPhase());
-
-	// Copy the range in case the original list of listeners get modified during ProcessEvent.
-	const Listeners listeners_range(begin, end);
-
-	for(const EventListenerEntry& entry : listeners_range)
-	{
-		entry.listener->ProcessEvent(event);
-		
-		if (!event.IsImmediatePropagating())
-			break;
-	}
-
-	const bool do_default_action = ((unsigned int)phase & (unsigned int)default_action_phase);
-
-	// Do the default action unless we have been cancelled.
-	if (do_default_action && event.IsPropagating())
-	{
-		element->ProcessDefaultAction(event);
-	}
-}
 
 }
 }

+ 14 - 5
Source/Core/EventDispatcher.h

@@ -37,6 +37,8 @@ namespace Core {
 
 class Element;
 class EventListener;
+struct EventListenersToExecute;
+
 struct EventListenerEntry {
 	EventListenerEntry(EventId id, EventListener* listener, bool in_capture_phase) : id(id), in_capture_phase(in_capture_phase), listener(listener) {}
 	EventId id;
@@ -44,6 +46,7 @@ struct EventListenerEntry {
 	EventListener* listener;
 };
 
+
 /**
 	The Event Dispatcher manages a list of event listeners and triggers the events via EventHandlers
 	whenever requested.
@@ -76,13 +79,16 @@ public:
 	/// Detaches all events from this dispatcher and all child dispatchers.
 	void DetachAllEvents();
 
-	/// Dispatches the specified event with element as the target
-	/// @param[in] target_element The target element of the event
-	/// @param[in] name The name of the event
+	/// Dispatches the specified event.
+	/// @param[in] target_element The element to target
+	/// @param[in] id The id of the event
+	/// @param[in] type The type of the event
 	/// @param[in] parameters The event parameters
 	/// @param[in] interruptible Can the event propagation be stopped
+	/// @param[in] bubbles True if the event should execute the bubble phase
+	/// @param[in] default_action_phase The phases to execute default actions in
 	/// @return True if the event was not consumed (ie, was prevented from propagating by an element), false if it was.
-	bool DispatchEvent(Element* element, EventId id, const String& type, const Dictionary& parameters, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase);
+	static bool DispatchEvent(Element* target_element, EventId id, const String& type, const Dictionary& parameters, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase);
 
 	/// Returns event types with number of listeners for debugging.
 	/// @return Summary of attached listeners.
@@ -96,9 +102,12 @@ private:
 	typedef std::vector< EventListenerEntry > Listeners;
 	Listeners listeners;
 
-	void TriggerEvents(Event& event, DefaultActionPhase default_action_phase);
+	// Collect all the listeners from this dispatcher that are allowed to execute given the input arguments.
+	void CollectListeners(int dom_distance_from_target, EventId event_id, EventPhase phases_to_execute, DefaultActionPhase default_action_phase, std::vector<EventListenersToExecute>& collect_listeners);
 };
 
+
+
 }
 }
 

+ 61 - 0
Source/Core/ObserverPtr.cpp

@@ -0,0 +1,61 @@
+/*
+ * 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 "precompiled.h"
+#include "../../Include/RmlUi/Core/ObserverPtr.h"
+#include "Pool.h"
+
+namespace Rml {
+namespace Core {
+
+
+static Pool< ObserverPtrBlock >& GetPool()
+{
+	// Wrap pool in a function to ensure it is initialized before use.
+	static Pool< ObserverPtrBlock > pool(400, true);
+	return pool;
+}
+
+
+void DeallocateObserverPtrBlockIfEmpty(ObserverPtrBlock* block) {
+	RMLUI_ASSERT(block->num_observers >= 0);
+	if (block->num_observers == 0 && block->pointed_to_object == nullptr)
+	{
+		GetPool().DestroyAndDeallocate(block);
+	}
+}
+
+ObserverPtrBlock* AllocateObserverPtrBlock()
+{
+	return GetPool().AllocateAndConstruct();
+}
+
+
+
+}
+}

+ 8 - 4
Source/Core/StringUtilities.cpp

@@ -28,7 +28,6 @@
 
 #include "precompiled.h"
 #include "../../Include/RmlUi/Core/StringUtilities.h"
-#include <ctype.h>
 #include <stdio.h>
 #include <stdarg.h>
 
@@ -84,7 +83,12 @@ String CreateString(size_t max_size, const char* format, ...)
 
 String StringUtilities::ToLower(const String& string) {
 	String str_lower = string;
-	std::transform(str_lower.begin(), str_lower.end(), str_lower.begin(), ::tolower);
+	std::transform(str_lower.begin(), str_lower.end(), str_lower.begin(), [](char c) {
+		if (c >= 'A' && c <= 'Z')
+			c += char( 'a' - 'A' );
+		return c;
+		}
+	);
 	return str_lower;
 }
 
@@ -151,7 +155,7 @@ void StringUtilities::ExpandString(StringList& string_list, const String& string
 			start_ptr = nullptr;
 		}
 		// Otherwise if its not white space or we're in quote mode, advance the pointers
-		else if (!isspace(*ptr) || quote)
+		else if (!IsWhitespace(*ptr) || quote)
 		{
 			if (!start_ptr)
 				start_ptr = ptr;
@@ -198,7 +202,7 @@ void StringUtilities::ExpandString(StringList& string_list, const String& string
 			start_ptr = nullptr;
 		}
 		// Otherwise if its not white space or we're in quote mode, advance the pointers
-		else if (!isspace(*ptr) || quote_mode_depth > 0)
+		else if (!IsWhitespace(*ptr) || quote_mode_depth > 0)
 		{
 			if (!start_ptr)
 				start_ptr = ptr;

+ 17 - 16
Source/Debugger/ElementInfo.cpp

@@ -111,6 +111,7 @@ ElementInfo::ElementInfo(const Core::String& tag) : Core::ElementDocument(tag)
 	show_source_element = true;
 	update_source_element = true;
 	force_update_once = false;
+	title_dirty = true;
 	previous_update_time = 0.0;
 
 	RMLUI_ASSERT(TestPrettyFormat("0.15", "0.15"));
@@ -188,6 +189,12 @@ void ElementInfo::OnUpdate()
 			UpdateSourceElement();
 		}
 	}
+
+	if (title_dirty)
+	{
+		UpdateTitle();
+		title_dirty = false;
+	}
 }
 
 // Called when an element is destroyed.
@@ -363,18 +370,15 @@ void ElementInfo::ProcessEvent(Core::Event& event)
 					hover_element = nullptr;
 				}
 
-				if(event.GetPhase() == Core::EventPhase::Bubble)
+				if (id == "show_source" && !show_source_element)
 				{
-					if (id == "show_source" && !show_source_element)
-					{
-						// Preview the source element view while hovering
-						show_source_element = true;
-					}
+					// Preview the source element view while hovering
+					show_source_element = true;
+				}
 
-					if (id == "show_source" || id == "update_source")
-					{
-						UpdateTitle();
-					}
+				if (id == "show_source" || id == "update_source")
+				{
+					title_dirty = true;
 				}
 			}
 			// Otherwise we just want to focus on the clicked element (unless it's on a debug element)
@@ -383,7 +387,7 @@ void ElementInfo::ProcessEvent(Core::Event& event)
 				hover_element = target_element;
 			}
 		}
-		else if (event == Core::EventId::Mouseout && event.GetPhase() == Core::EventPhase::Bubble)
+		else if (event == Core::EventId::Mouseout)
 		{
 			Core::Element* target_element = event.GetTargetElement();
 			Core::ElementDocument* owner_document = target_element->GetOwnerDocument();
@@ -399,8 +403,7 @@ void ElementInfo::ProcessEvent(Core::Event& event)
 
 				if (id == "show_source" || id == "update_source")
 				{
-					UpdateTitle();
-					event.StopPropagation();
+					title_dirty = true;
 				}
 			}
 		}
@@ -416,9 +419,7 @@ void ElementInfo::SetSourceElement(Core::Element* new_source_element)
 void ElementInfo::UpdateSourceElement()
 {
 	previous_update_time = Core::GetSystemInterface()->GetElapsedTime();
-
-	// Set the title
-	UpdateTitle();
+	title_dirty = true;
 
 	// Set the pseudo classes
 	if (Core::Element* pseudo = GetElementById("pseudo"))

+ 2 - 0
Source/Debugger/ElementInfo.h

@@ -86,6 +86,8 @@ private:
 	bool update_source_element;
 	// Forces an update to the source element during the next update loop.
 	bool force_update_once;
+	
+	bool title_dirty;
 
 	Core::Element* hover_element;
 	Core::Element* source_element;

+ 2 - 10
Source/Debugger/InfoSource.h

@@ -70,20 +70,12 @@ h3.strong
 	color: #900;
 	background-color: #eee;
 }
-#pseudo
-{
-	line-height: 1.4em;
-	padding: 2px 0;
-}
 #pseudo pseudo
 {
-	padding: 0 3dp;
+	padding: 0 8dp 0 3dp;
 	background-color: #ddd;
 	border: 2px #aaa;
-}
-#pseudo span
-{
-	padding-left: 3dp;
+	display: inline-block;
 }
 #pseudo pseudo.active
 {