Browse Source

Copy the event listeners before processing events (see #45). Check whether event is propagating between each dispatch.

Michael Ragazzon 6 years ago
parent
commit
7546033f34

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

@@ -137,7 +137,7 @@ panel
 	<button id="high_scores" onkeydown="hello">High Scores</button><br />
 	<button id="options">Options</button><br />
 	<button id="help">Help</button><br />
-	<button id="exit" onclick="exit">Exit</button>
+	<div><button id="exit" onclick="exit">Exit</button></div>
 </panel>
 <tab>Controls</tab>
 <panel>

+ 10 - 2
Samples/basic/demo/src/main.cpp

@@ -152,20 +152,28 @@ void GameLoop()
 class Event : public Rml::Core::EventListener
 {
 public:
-	Event(const Rml::Core::String& value) : value(value) {}
+	Event(const Rml::Core::String& value, Rml::Core::Element* element) : value(value), element(element) {}
 
 	void ProcessEvent(Rml::Core::Event& event) override
 	{
 		using namespace Rml::Core;
 
 		if (value == "exit")
+		{
+			element->GetParentNode()->SetInnerRML("<button onclick='confirm_exit'>Are you sure?</button>");
+			event.StopPropagation();
+		}
+		else if (value == "confirm_exit")
+		{
 			Shell::RequestExit();
+		}
 	}
 
 	void OnDetach(Rml::Core::Element* element) override { delete this; }
 
 private:
 	Rml::Core::String value;
+	Rml::Core::Element* element;
 };
 
 
@@ -175,7 +183,7 @@ class EventInstancer : public Rml::Core::EventListenerInstancer
 public:
 	Rml::Core::EventListener* InstanceEventListener(const Rml::Core::String& value, Rml::Core::Element* element) override
 	{
-		return new Event(value);
+		return new Event(value, element);
 	}
 };
 

+ 9 - 6
Source/Core/EventDispatcher.cpp

@@ -126,7 +126,7 @@ bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const S
 	}
 
 	event->SetPhase(EventPhase::Capture);
-	// Capture phase - root, to target (only events that have registered as capture events)
+	// 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--) 
 	{
@@ -208,12 +208,15 @@ void EventDispatcher::TriggerEvents(Event& event, DefaultActionPhase default_act
 	else if (phase == EventPhase::Bubble)
 		std::tie(begin, end) = std::equal_range(listeners.begin(), listeners.end(), EventListenerEntry(event.GetId(), nullptr, false), CompareIdPhase());
 
-	// Convert to indices. If any listeners are added or removed during ProcessEvent, this guards against iterator invalidation.
-	// Note: The result of listener add/remove may then instead be that a listener is skipped or repeated.
-	const size_t i_end = (size_t)(end - listeners.begin());
-	for (size_t i = (size_t)(begin - listeners.begin()); i < i_end && i < listeners.size(); ++i)
+	// 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)
 	{
-		listeners[i].listener->ProcessEvent(event);
+		entry.listener->ProcessEvent(event);
+		
+		if (!event.IsPropagating())
+			break;
 	}
 
 	const bool do_default_action = ((unsigned int)phase & (unsigned int)default_action_phase);