Browse Source

Optionally use EventId on add/remove listener. In the EventDispatcher, replace the map<vector> with a flat, manually sorted vector.

Michael Ragazzon 6 years ago
parent
commit
fd44d9cfcb

+ 4 - 0
Include/Rocket/Core/Element.h

@@ -479,11 +479,15 @@ public:
 	/// @param[in] listener The listener object to be attached.
 	/// @param[in] in_capture_phase True to attach in the capture phase, false in bubble phase.
 	void AddEventListener(const String& event, EventListener* listener, bool in_capture_phase = false);
+	/// Adds an event listener to this element by id.
+	void AddEventListener(EventId id, EventListener* listener, bool in_capture_phase = false);
 	/// Removes an event listener from this element.
 	/// @param[in] event Event to detach from.
 	/// @param[in] listener The listener object to be detached.
 	/// @param[in] in_capture_phase True to detach from the capture phase, false from the bubble phase.
 	void RemoveEventListener(const String& event, EventListener* listener, bool in_capture_phase = false);
+	/// Removes an event listener from this element by id.
+	void RemoveEventListener(EventId id, EventListener* listener, bool in_capture_phase = false);
 	/// Sends an event to this element.
 	/// @param[in] type Event type in string form.
 	/// @param[in] parameters The event parameters.

+ 3 - 3
Samples/basic/animation/src/main.cpp

@@ -370,9 +370,9 @@ int main(int ROCKET_UNUSED_PARAMETER(argc), char** ROCKET_UNUSED_PARAMETER(argv)
 	Shell::LoadFonts("assets/");
 
 	window = new DemoWindow("Animation sample", Rocket::Core::Vector2f(81, 100), context);
-	window->GetDocument()->AddEventListener("keydown", new Event("hello"));
-	window->GetDocument()->AddEventListener("keyup", new Event("hello"));
-	window->GetDocument()->AddEventListener("animationend", new Event("hello"));
+	window->GetDocument()->AddEventListener(Rocket::Core::EventId::Keydown, new Event("hello"));
+	window->GetDocument()->AddEventListener(Rocket::Core::EventId::Keyup, new Event("hello"));
+	window->GetDocument()->AddEventListener(Rocket::Core::EventId::Animationend, new Event("hello"));
 
 
 	Shell::EventLoop(GameLoop);

+ 3 - 3
Samples/basic/benchmark/src/main.cpp

@@ -339,9 +339,9 @@ int main(int ROCKET_UNUSED_PARAMETER(argc), char** ROCKET_UNUSED_PARAMETER(argv)
 	Shell::LoadFonts("assets/");
 
 	window = new DemoWindow("Benchmark sample", Rocket::Core::Vector2f(81, 100), context);
-	window->GetDocument()->AddEventListener("keydown", new Event("hello"));
-	window->GetDocument()->AddEventListener("keyup", new Event("hello"));
-	window->GetDocument()->AddEventListener("animationend", new Event("hello"));
+	window->GetDocument()->AddEventListener(Rocket::Core::EventId::Keydown, new Event("hello"));
+	window->GetDocument()->AddEventListener(Rocket::Core::EventId::Keyup, new Event("hello"));
+	window->GetDocument()->AddEventListener(Rocket::Core::EventId::Animationend, new Event("hello"));
 
 
 	Shell::EventLoop(GameLoop);

+ 1 - 1
Samples/basic/drag/src/DragListener.cpp

@@ -33,7 +33,7 @@ static DragListener drag_listener;
 // Registers an element as being a container of draggable elements.
 void DragListener::RegisterDraggableContainer(Rocket::Core::Element* element)
 {
-	element->AddEventListener("dragdrop", &drag_listener);
+	element->AddEventListener(Rocket::Core::EventId::Dragdrop, &drag_listener);
 }
 
 void DragListener::ProcessEvent(Rocket::Core::Event& event)

+ 3 - 3
Samples/invaders/src/ElementGame.cpp

@@ -100,8 +100,8 @@ void ElementGame::OnChildAdd(Rocket::Core::Element* element)
 
 	if (element == this)
 	{
-		GetOwnerDocument()->AddEventListener("load", this);
-		GetOwnerDocument()->AddEventListener("keydown", this);
-		GetOwnerDocument()->AddEventListener("keyup", this);
+		GetOwnerDocument()->AddEventListener(Rocket::Core::EventId::Load, this);
+		GetOwnerDocument()->AddEventListener(Rocket::Core::EventId::Keydown, this);
+		GetOwnerDocument()->AddEventListener(Rocket::Core::EventId::Keyup, this);
 	}
 }

+ 3 - 3
Samples/luainvaders/src/ElementGame.cpp

@@ -97,8 +97,8 @@ void ElementGame::OnChildAdd(Rocket::Core::Element* element)
 
 	if (element == this)
 	{
-		GetOwnerDocument()->AddEventListener("load", this);
-		GetOwnerDocument()->AddEventListener("keydown", this);
-		GetOwnerDocument()->AddEventListener("keyup", this);
+		GetOwnerDocument()->AddEventListener(Rocket::Core::EventId::Load, this);
+		GetOwnerDocument()->AddEventListener(Rocket::Core::EventId::Keydown, this);
+		GetOwnerDocument()->AddEventListener(Rocket::Core::EventId::Keyup, this);
 	}
 }

+ 2 - 2
Source/Controls/ElementTabSet.cpp

@@ -179,7 +179,7 @@ void ElementTabSet::OnChildAdd(Core::Element* child)
 	{
 		// Set up the new button and append it
 		child->SetProperty("display", Core::Property(Core::Style::Display::InlineBlock));
-		child->AddEventListener("click", this);
+		child->AddEventListener(Core::EventId::Click, this);
 
 		if (child->GetParentNode()->GetChild(active_tab) == child)
 			child->SetPseudoClass("selected", true);
@@ -203,7 +203,7 @@ void ElementTabSet::OnChildRemove(Core::Element* child)
 	// If its a tab, remove its event listener
 	if (child->GetParentNode() == GetChildByTag("tabs"))
 	{
-		child->RemoveEventListener("click", this);
+		child->RemoveEventListener(Core::EventId::Click, this);
 	}
 }
 

+ 11 - 11
Source/Controls/WidgetDropDown.cpp

@@ -58,10 +58,10 @@ WidgetDropDown::WidgetDropDown(ElementFormControl* element)
 	selection_element->SetProperty("z-index", Core::Property(1.0f, Core::Property::NUMBER));
 	selection_element->SetProperty("clip", Core::Property(Core::Style::Clip::None));
 
-	parent_element->AddEventListener("click", this, true);
-	parent_element->AddEventListener("blur", this);
-	parent_element->AddEventListener("focus", this);
-	parent_element->AddEventListener("keydown", this, true);
+	parent_element->AddEventListener(Core::EventId::Click, this, true);
+	parent_element->AddEventListener(Core::EventId::Blur, this);
+	parent_element->AddEventListener(Core::EventId::Focus, this);
+	parent_element->AddEventListener(Core::EventId::Keydown, this, true);
 
 	// Add the elements to our parent element.
 	parent_element->AppendChild(button_element, false);
@@ -75,12 +75,12 @@ WidgetDropDown::~WidgetDropDown()
 	//   Not always a problem, but sometimes it invalidates the layout lock in Element::RemoveChild, which results in a permanently corrupted document.
 	//   However, we do need to remove events of children.
 	for(auto& option : options)
-		option.GetElement()->RemoveEventListener("click", this);
+		option.GetElement()->RemoveEventListener(Core::EventId::Click, this);
 
-	parent_element->RemoveEventListener("click", this, true);
-	parent_element->RemoveEventListener("blur", this);
-	parent_element->RemoveEventListener("focus", this);
-	parent_element->RemoveEventListener("keydown", this, true);
+	parent_element->RemoveEventListener(Core::EventId::Click, this, true);
+	parent_element->RemoveEventListener(Core::EventId::Blur, this);
+	parent_element->RemoveEventListener(Core::EventId::Focus, this);
+	parent_element->RemoveEventListener(Core::EventId::Keydown, this, true);
 
 	button_element->RemoveReference();
 	selection_element->RemoveReference();
@@ -225,7 +225,7 @@ int WidgetDropDown::AddOption(const Rocket::Core::String& rml, const Rocket::Cor
 	element->SetProperty("display", Core::Property(Core::Style::Display::Block));
 	element->SetProperty("clip", Core::Property(Core::Style::Clip::Auto));
 	element->SetInnerRML(rml);
-	element->AddEventListener("click", this);
+	element->AddEventListener(Core::EventId::Click, this);
 
 	int option_index;
 	if (before < 0 ||
@@ -260,7 +260,7 @@ void WidgetDropDown::RemoveOption(int index)
 		return;
 
 	// Remove the listener and delete the option element.
-	options[index].GetElement()->RemoveEventListener("click", this);
+	options[index].GetElement()->RemoveEventListener(Core::EventId::Click, this);
 	selection_element->RemoveChild(options[index].GetElement());
 	options.erase(options.begin() + index);
 

+ 20 - 20
Source/Controls/WidgetSlider.cpp

@@ -67,16 +67,16 @@ WidgetSlider::~WidgetSlider()
 		parent->RemoveChild(track);
 	}
 
-	parent->RemoveEventListener("blur", this);
-	parent->RemoveEventListener("focus", this);
-	parent->RemoveEventListener("keydown", this, true);
-	parent->RemoveEventListener("mousedown", this);
-	parent->RemoveEventListener("mouseup", this);
-	parent->RemoveEventListener("mouseout", this);
-
-	parent->RemoveEventListener("drag", this);
-	parent->RemoveEventListener("dragstart", this);
-	parent->RemoveEventListener("dragend", this);
+	using Core::EventId;
+	parent->RemoveEventListener(EventId::Blur, this);
+	parent->RemoveEventListener(EventId::Focus, this);
+	parent->RemoveEventListener(EventId::Keydown, this, true);
+	parent->RemoveEventListener(EventId::Mousedown, this);
+	parent->RemoveEventListener(EventId::Mouseup, this);
+	parent->RemoveEventListener(EventId::Mouseout, this);
+	parent->RemoveEventListener(EventId::Drag, this);
+	parent->RemoveEventListener(EventId::Dragstart, this);
+	parent->RemoveEventListener(EventId::Dragend, this);
 
 	for (int i = 0; i < 2; i++)
 	{
@@ -137,16 +137,16 @@ bool WidgetSlider::Initialise()
 
 	// Attach the listeners
 	// All listeners are attached to parent, ensuring that we don't get duplicate events when it bubbles from child to parent
-	parent->AddEventListener("blur", this);
-	parent->AddEventListener("focus", this);
-	parent->AddEventListener("keydown", this, true);
-	parent->AddEventListener("mousedown", this);
-	parent->AddEventListener("mouseup", this);
-	parent->AddEventListener("mouseout", this);
-
-	parent->AddEventListener("drag", this);
-	parent->AddEventListener("dragstart", this);
-	parent->AddEventListener("dragend", this);
+	using Core::EventId;
+	parent->AddEventListener(EventId::Blur, this);
+	parent->AddEventListener(EventId::Focus, this);
+	parent->AddEventListener(EventId::Keydown, this, true);
+	parent->AddEventListener(EventId::Mousedown, this);
+	parent->AddEventListener(EventId::Mouseup, this);
+	parent->AddEventListener(EventId::Mouseout, this);
+	parent->AddEventListener(EventId::Drag, this);
+	parent->AddEventListener(EventId::Dragstart, this);
+	parent->AddEventListener(EventId::Dragend, this);
 
 	return true;
 }

+ 12 - 12
Source/Controls/WidgetTextInput.cpp

@@ -47,12 +47,12 @@ WidgetTextInput::WidgetTextInput(ElementFormControl* _parent) : internal_dimensi
 	parent->SetProperty("drag", Core::Property(Core::Style::Drag::Drag));
 	parent->SetClientArea(Rocket::Core::Box::CONTENT);
 
-	parent->AddEventListener("keydown", this, true);
-	parent->AddEventListener("textinput", this, true);
-	parent->AddEventListener("focus", this, true);
-	parent->AddEventListener("blur", this, true);
-	parent->AddEventListener("mousedown", this, true);
-	parent->AddEventListener("drag", this, true);
+	parent->AddEventListener(Core::EventId::Keydown, this, true);
+	parent->AddEventListener(Core::EventId::Textinput, this, true);
+	parent->AddEventListener(Core::EventId::Focus, this, true);
+	parent->AddEventListener(Core::EventId::Blur, this, true);
+	parent->AddEventListener(Core::EventId::Mousedown, this, true);
+	parent->AddEventListener(Core::EventId::Drag, this, true);
 
 	text_element = dynamic_cast< Core::ElementText* >(Core::Factory::InstanceElement(parent, "#text", "#text", Rocket::Core::XMLAttributes()));
 	selected_text_element = dynamic_cast< Core::ElementText* >(Core::Factory::InstanceElement(parent, "#text", "#text", Rocket::Core::XMLAttributes()));
@@ -97,12 +97,12 @@ WidgetTextInput::WidgetTextInput(ElementFormControl* _parent) : internal_dimensi
 
 WidgetTextInput::~WidgetTextInput()
 {
-	parent->RemoveEventListener("keydown", this, true);
-	parent->RemoveEventListener("textinput", this, true);
-	parent->RemoveEventListener("focus", this, true);
-	parent->RemoveEventListener("blur", this, true);
-	parent->RemoveEventListener("mousedown", this, true);
-	parent->RemoveEventListener("drag", this, true);
+	parent->RemoveEventListener(Core::EventId::Keydown, this, true);
+	parent->RemoveEventListener(Core::EventId::Textinput, this, true);
+	parent->RemoveEventListener(Core::EventId::Focus, this, true);
+	parent->RemoveEventListener(Core::EventId::Blur, this, true);
+	parent->RemoveEventListener(Core::EventId::Mousedown, this, true);
+	parent->RemoveEventListener(Core::EventId::Drag, this, true);
 
 	// Remove all the children added by the text widget.
 	parent->RemoveChild(text_element);

+ 13 - 0
Source/Core/Element.cpp

@@ -1301,6 +1301,12 @@ void Element::AddEventListener(const String& event, EventListener* listener, boo
 	event_dispatcher->AttachEvent(id, listener, in_capture_phase);
 }
 
+// Adds an event listener
+void Element::AddEventListener(EventId id, EventListener* listener, bool in_capture_phase)
+{
+	event_dispatcher->AttachEvent(id, listener, in_capture_phase);
+}
+
 // Removes an event listener from this element.
 void Element::RemoveEventListener(const String& event, EventListener* listener, bool in_capture_phase)
 {
@@ -1308,6 +1314,13 @@ void Element::RemoveEventListener(const String& event, EventListener* listener,
 	event_dispatcher->DetachEvent(id, listener, in_capture_phase);
 }
 
+// Removes an event listener from this element.
+void Element::RemoveEventListener(EventId id, EventListener* listener, bool in_capture_phase)
+{
+	event_dispatcher->DetachEvent(id, listener, in_capture_phase);
+}
+
+
 // Dispatches the specified event
 bool Element::DispatchEvent(const String& type, const Dictionary& parameters)
 {

+ 85 - 74
Source/Core/EventDispatcher.cpp

@@ -36,6 +36,19 @@
 namespace Rocket {
 namespace Core {
 
+
+bool operator==(EventListenerEntry a, EventListenerEntry b) { return a.id == b.id && a.in_capture_phase == b.in_capture_phase && a.listener == b.listener; }
+bool operator!=(EventListenerEntry a, EventListenerEntry b) { return !(a == b); }
+
+struct CompareId {
+	bool operator()(EventListenerEntry a, EventListenerEntry b) const { return a.id < b.id; }
+}; 
+struct CompareIdPhase {
+	bool operator()(EventListenerEntry a, EventListenerEntry b) const { return std::tie(a.id, a.in_capture_phase) < std::tie(b.id, b.in_capture_phase); }
+};
+
+
+
 EventDispatcher::EventDispatcher(Element* _element)
 {
 	element = _element;
@@ -44,70 +57,51 @@ EventDispatcher::EventDispatcher(Element* _element)
 EventDispatcher::~EventDispatcher()
 {
 	// Detach from all event dispatchers
-	for (Events::iterator event_itr = events.begin(); event_itr != events.end(); ++event_itr)
-	{
-		for (Listeners::iterator listener_itr = (*event_itr).second.begin(); listener_itr != (*event_itr).second.end(); ++listener_itr)
-		{
-			(*listener_itr).listener->OnDetach(element);
-		}
-	}
+	for (const auto& event : listeners)
+		event.listener->OnDetach(element);
 }
 
 void EventDispatcher::AttachEvent(EventId id, EventListener* listener, bool in_capture_phase)
 {
-	// See if event type exists already
-	Events::iterator event_itr = events.find(id);
+	EventListenerEntry entry(id, listener, in_capture_phase);
 
-	if (event_itr == events.end())
-	{
-		// No, add listener to new event type entry
-		event_itr = events.emplace(id, Listeners{ Listener(listener, in_capture_phase) }).first;
-	}
-	else
+	// The entries are sorted by (id,phase). Find the bounds of this sort, then find the entry.
+	auto range = std::equal_range(listeners.begin(), listeners.end(), entry, CompareIdPhase());
+	auto it = std::find(range.first, range.second, entry);
+
+	if(it == range.second)
 	{
-		// Yes, add listener to the existing list of events for the type
-		(*event_itr).second.emplace_back(listener, in_capture_phase);
+		// No existing entry found, add it to the end of the (id, phase) range
+		listeners.emplace(it, entry);
+		listener->OnAttach(element);
 	}
-
-	listener->OnAttach(element);
 }
 
+
 void EventDispatcher::DetachEvent(EventId id, EventListener* listener, bool in_capture_phase)
 {
-	// Look up the event
-	Events::iterator event_itr = events.find(id);
-
-	// Bail if we can't find the event
-	if (event_itr == events.end())
-	{
-		return;
-	}
-
-	// Find the relevant listener and erase it
-	Listeners::iterator listener_itr = (*event_itr).second.begin();
-	while (listener_itr != (*event_itr).second.end())
+	EventListenerEntry entry(id, listener, in_capture_phase);
+	
+	// The entries are sorted by (id,phase). Find the bounds of this sort, then find the entry.
+	// We could also just do a linear search over all the entries, which might be faster for low number of entries.
+	auto range = std::equal_range(listeners.begin(), listeners.end(), entry, CompareIdPhase());
+	auto it = std::find(range.first, range.second, entry);
+
+	if (it != range.second)
 	{
-		if ((*listener_itr).listener == listener && (*listener_itr).in_capture_phase == in_capture_phase)
-		{
-			listener_itr = (*event_itr).second.erase(listener_itr);
-			listener->OnDetach(element);
-		}
-		else
-			++listener_itr;
+		// We found our listener, remove it
+		listeners.erase(it);
+		listener->OnDetach(element);
 	}
 }
 
 // Detaches all events from this dispatcher and all child dispatchers.
 void EventDispatcher::DetachAllEvents()
 {
-	for (Events::iterator event_iterator = events.begin(); event_iterator != events.end(); ++event_iterator)
-	{
-		Listeners& listeners = event_iterator->second;
-		for (size_t i = 0; i < listeners.size(); ++i)
-			listeners[i].listener->OnDetach(element);
-	}
+	for (const auto& event : listeners)
+		event.listener->OnDetach(element);
 
-	events.clear();
+	listeners.clear();
 
 	for (int i = 0; i < element->GetNumChildren(true); ++i)
 		element->GetChild(i)->GetEventDispatcher()->DetachAllEvents();
@@ -137,7 +131,7 @@ bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const S
 	{
 		EventDispatcher* dispatcher = elements[i]->GetEventDispatcher();
 		event->SetCurrentElement(elements[i]);
-		dispatcher->TriggerEvents(event, default_action_phase);
+		dispatcher->TriggerEvents(*event, default_action_phase);
 	}
 
 	// Target phase - direct at the target
@@ -145,7 +139,7 @@ bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const S
 	{
 		event->SetPhase(EventPhase::Target);
 		event->SetCurrentElement(target_element);
-		TriggerEvents(event, default_action_phase);
+		TriggerEvents(*event, default_action_phase);
 	}
 
 	// Bubble phase - target to root (normal event bindings)
@@ -156,7 +150,7 @@ bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const S
 		{
 			EventDispatcher* dispatcher = elements[i]->GetEventDispatcher();
 			event->SetCurrentElement(elements[i]);
-			dispatcher->TriggerEvents(event, default_action_phase);
+			dispatcher->TriggerEvents(*event, default_action_phase);
 		}
 	}
 
@@ -168,45 +162,62 @@ bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const S
 String EventDispatcher::ToString() const
 {
 	String result;
-	for (auto nvp : events)
+
+	if (listeners.empty())
+		return result;
+
+	auto add_to_result = [&result](EventId id, int count) {
+		const EventSpecification& specification = EventSpecificationInterface::Get(id);
+		result += CreateString(specification.type.size() + 32, "%s (%d), ", specification.type.c_str(), count);
+	};
+
+	EventId previous_id = listeners[0].id;
+	int count = 0;
+	for (const auto& listener : listeners)
 	{
-		const EventSpecification& specification = EventSpecificationInterface::Get(nvp.first);
-		result += CreateString(specification.type.size() + 32, "%s (%d), ", specification.type.c_str(), static_cast<int>(nvp.second.size()));
+		if (listener.id != previous_id)
+		{
+			add_to_result(previous_id, count);
+			previous_id = listener.id;
+			count = 0;
+		}
+		count++;
 	}
+
+	if (count > 0)
+		add_to_result(previous_id, count);
+
 	if (result.size() > 2) 
-	{
 		result.resize(result.size() - 2);
-	}
+
 	return result;
 }
 
-void EventDispatcher::TriggerEvents(Event* event, DefaultActionPhase default_action_phase)
+void EventDispatcher::TriggerEvents(Event& event, DefaultActionPhase default_action_phase)
 {
-	const EventPhase phase = event->GetPhase();
-
-	// Look up the event
-	Events::iterator itr = events.find(event->GetId());
-
-	if (itr != events.end())
+	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());
+
+	for (auto it = begin; it != end; ++it)
 	{
-		// Dispatch all actions
-		Listeners& listeners = (*itr).second;
-		for (size_t i = 0; i < listeners.size(); i++)
-		{
-			if (phase == EventPhase::Target
-				|| (phase == EventPhase::Capture && listeners[i].in_capture_phase)
-				|| (phase == EventPhase::Bubble && !listeners[i].in_capture_phase))
-			{
-				listeners[i].listener->ProcessEvent(*event);
-			}
-		}
+		it->listener->ProcessEvent(event);
 	}
 
-	const bool do_default_action = ((int)phase & (int)default_action_phase);
+	const bool do_default_action = ((unsigned int)phase & (unsigned int)default_action_phase);
 
-	if (do_default_action && event->IsPropagating())
+	// Do the default action unless we have been cancelled.
+	if (do_default_action && event.IsPropagating())
 	{
-		element->ProcessDefaultAction(*event);
+		element->ProcessDefaultAction(event);
 	}
 }
 

+ 14 - 14
Source/Core/EventDispatcher.h

@@ -36,10 +36,15 @@ namespace Core {
 
 class Element;
 class EventListener;
-
+struct EventListenerEntry {
+	EventListenerEntry(EventId id, EventListener* listener, bool in_capture_phase) : id(id), in_capture_phase(in_capture_phase), listener(listener) {}
+	EventId id;
+	bool in_capture_phase;
+	EventListener* listener;
+};
 
 /**
-	The Event Dispatcher manages a list of event listeners (based on URL) and triggers the events via EventHandlers
+	The Event Dispatcher manages a list of event listeners and triggers the events via EventHandlers
 	whenever requested.
 
 	@author Lloyd Weehuizen
@@ -52,7 +57,7 @@ public:
 	/// @param element Element this dispatcher acts on
 	EventDispatcher(Element* element);
 
-	// Destructor
+	/// Destructor
 	~EventDispatcher();
 
 	/// Attaches a new listener to the specified event name
@@ -85,17 +90,12 @@ public:
 private:
 	Element* element;
 
-	struct Listener
-	{
-		Listener(EventListener* _listener, bool _in_capture_phase) : listener(_listener), in_capture_phase(_in_capture_phase) {}
-		EventListener* listener;
-		bool in_capture_phase;
-	};
-	typedef std::vector< Listener > Listeners;
-	typedef SmallUnorderedMap< EventId, Listeners > Events;
-	Events events;
-
-	void TriggerEvents(Event* event, DefaultActionPhase default_action_phase);
+	// Listeners are sorted first by (id, phase) and then by the order in which the listener was inserted.
+	// All listeners added are unique.
+	typedef std::vector< EventListenerEntry > Listeners;
+	Listeners listeners;
+
+	void TriggerEvents(Event& event, DefaultActionPhase default_action_phase);
 };
 
 }

+ 12 - 12
Source/Core/WidgetSlider.cpp

@@ -63,20 +63,20 @@ WidgetSlider::~WidgetSlider()
 {
 	if (bar != NULL)
 	{
-		bar->RemoveEventListener(DRAG, this);
-		bar->RemoveEventListener(DRAGSTART, this);
+		bar->RemoveEventListener(Core::EventId::Drag, this);
+		bar->RemoveEventListener(Core::EventId::Dragstart, this);
 	}
 
 	if (track != NULL)
-		track->RemoveEventListener(CLICK, this);
+		track->RemoveEventListener(Core::EventId::Click, this);
 
 	for (int i = 0; i < 2; i++)
 	{
 		if (arrows[i] != NULL)
 		{
-			arrows[i]->RemoveEventListener(MOUSEDOWN, this);
-			arrows[i]->RemoveEventListener(MOUSEUP, this);
-			arrows[i]->RemoveEventListener(MOUSEOUT, this);
+			arrows[i]->RemoveEventListener(Core::EventId::Mousedown, this);
+			arrows[i]->RemoveEventListener(Core::EventId::Mouseup, this);
+			arrows[i]->RemoveEventListener(Core::EventId::Mouseout, this);
 		}
 	}
 }
@@ -143,16 +143,16 @@ bool WidgetSlider::Initialise(Orientation _orientation)
 	arrows[1]->RemoveReference();
 
 	// Attach the listeners as appropriate.
-	bar->AddEventListener(DRAG, this);
-	bar->AddEventListener(DRAGSTART, this);
+	bar->AddEventListener(EventId::Drag, this);
+	bar->AddEventListener(EventId::Dragstart, this);
 
-	track->AddEventListener(CLICK, this);
+	track->AddEventListener(EventId::Click, this);
 
 	for (int i = 0; i < 2; i++)
 	{
-		arrows[i]->AddEventListener(MOUSEDOWN, this);
-		arrows[i]->AddEventListener(MOUSEUP, this);
-		arrows[i]->AddEventListener(MOUSEOUT, this);
+		arrows[i]->AddEventListener(EventId::Mousedown, this);
+		arrows[i]->AddEventListener(EventId::Mouseup, this);
+		arrows[i]->AddEventListener(EventId::Mouseout, this);
 	}
 
 	return true;

+ 2 - 2
Source/Debugger/ElementLog.cpp

@@ -88,7 +88,7 @@ bool ElementLog::Initialise()
 	message_content = GetElementById("content");
 	if (message_content)
 	{
-		message_content->AddEventListener("resize", this);
+		message_content->AddEventListener(Core::EventId::Resize, this);
 	}
 
 	Core::StyleSheet* style_sheet = Core::Factory::InstanceStyleSheetString(Core::String(common_rcss) + Core::String(log_rcss));
@@ -112,7 +112,7 @@ bool ElementLog::Initialise()
 
 	Core::Element* button = beacon->GetFirstChild();
 	if (button != NULL)
-		beacon->GetFirstChild()->AddEventListener("click", this);
+		beacon->GetFirstChild()->AddEventListener(Core::EventId::Click, this);
 
 	style_sheet = Core::Factory::InstanceStyleSheetString(Core::String(common_rcss) + Core::String(beacon_rcss));
 	if (style_sheet == NULL)

+ 3 - 3
Source/Debugger/Plugin.cpp

@@ -321,13 +321,13 @@ bool Plugin::LoadMenuElement()
 
 	// Attach to the buttons.
 	Core::Element* event_log_button = menu_element->GetElementById("event-log-button");
-	event_log_button->AddEventListener("click", this);
+	event_log_button->AddEventListener(Rocket::Core::EventId::Click, this);
 
 	Core::Element* element_info_button = menu_element->GetElementById("debug-info-button");
-	element_info_button->AddEventListener("click", this);
+	element_info_button->AddEventListener(Rocket::Core::EventId::Click, this);
 
 	Core::Element* outlines_button = menu_element->GetElementById("outlines-button");
-	outlines_button->AddEventListener("click", this);
+	outlines_button->AddEventListener(Rocket::Core::EventId::Click, this);
 
 	return true;
 }