浏览代码

Add Event::PreventDefault function. Change behavior, Event::StopPropagation no longer cancels default actions. Clean up event dispatcher.

Michael Ragazzon 6 年之前
父节点
当前提交
d7ba65477f
共有 5 个文件被更改,包括 106 次插入82 次删除
  1. 29 28
      Include/RmlUi/Core/Event.h
  2. 2 2
      Include/RmlUi/Core/ObserverPtr.h
  3. 17 0
      Source/Core/Event.cpp
  4. 56 50
      Source/Core/EventDispatcher.cpp
  5. 2 2
      Source/Core/EventDispatcher.h

+ 29 - 28
Include/RmlUi/Core/Event.h

@@ -42,7 +42,7 @@ class EventInstancer;
 struct EventSpecification;
 
 enum class EventPhase { None, Capture = 1, Target = 2, Bubble = 4 };
-enum class DefaultActionPhase { None, Target = (int)EventPhase::Target, Bubble = (int)EventPhase::Bubble, TargetAndBubble = ((int)Target | (int)Bubble) };
+enum class DefaultActionPhase { None, Target = (int)EventPhase::Target, TargetAndBubble = ((int)Target | (int)EventPhase::Bubble) };
 
 /**
 	An event that propogates through the element hierarchy. Events follow the DOM3 event specification. See
@@ -65,45 +65,45 @@ public:
 	/// Destructor
 	virtual ~Event();
 
-
 	/// Get the current propagation phase.
-	/// @return Current phase the event is in.
 	EventPhase GetPhase() const;
 	/// Set the current propagation phase
-	/// @param phase Switch the phase the event is in
 	void SetPhase(EventPhase phase);
 
 	/// Set the current element in the propagation.
-	/// @param[in] element The current element.
 	void SetCurrentElement(Element* element);
 	/// Get the current element in the propagation.
-	/// @return The current element in propagation.
 	Element* GetCurrentElement() const;
-
-	/// Get the target element
-	/// @return The target element of this event
+	/// Get the target element of this event.
 	Element* GetTargetElement() const;
 
 	/// Get the event type.
 	const String& GetType() const;
 	/// Get the event id.
 	EventId GetId() const;
-	/// Checks if the event is of a certain type.
-	/// @param type The name of the type to check for.
-	/// @return True if the event is of the requested type, false otherwise.
-	bool operator==(const String& type) const;
-	/// Checks if the event is of a certain id.
-	bool operator==(EventId id) const;
 
+	/// Stops propagation of the event if it is interruptible, but finish all listeners on the current element.
+	void StopPropagation();
+	/// Stops propagation of the event if it is interruptible, including to any other listeners on the current element.
+	void StopImmediatePropagation();
+	/// Prevents the default actions from being performed.
+	void PreventDefault();
+
+	/// Returns true if the event can be interrupted, that is, stopped from propagating.
+	bool IsInterruptible() const;
 	/// Returns true if the event is still propagating.
 	bool IsPropagating() const;
 	/// Returns true if the event is still immediate propagating.
 	bool IsImmediatePropagating() const;
+	/// Returns true if the default actions to be executed by this event has been prevented.
+	bool IsDefaultPrevented() const;
 
-	/// Stops propagation of the event, but finish all listeners on the current element.
-	void StopPropagation();
-	/// Stops propagation of the event, including to any other listeners on the current element.
-	void StopImmediatePropagation();
+	/// Checks if the event is of a certain type.
+	/// @param type The name of the type to check for.
+	/// @return True if the event is of the requested type, false otherwise.
+	bool operator==(const String& type) const;
+	/// Checks if the event is of a certain id.
+	bool operator==(EventId id) const;
 
 	/// Returns the value of one of the event's parameters.
 	/// @param key[in] The name of the desired parameter.
@@ -121,14 +121,6 @@ public:
 	/// Note: Only specified for events with 'mouse_x' and 'mouse_y' parameters.
 	const Vector2f& GetUnprojectedMouseScreenPos() const;
 
-private:
-	/// Release this event.
-	void Release() override;
-
-	/// Project the mouse coordinates to the current element to enable
-	/// interacting with transformed elements.
-	void ProjectMouse(Element* element);
-
 protected:
 	Dictionary parameters;
 
@@ -136,17 +128,26 @@ protected:
 	Element* current_element;
 
 private:
+	/// Project the mouse coordinates to the current element to enable
+	/// interacting with transformed elements.
+	void ProjectMouse(Element* element);
+
+	/// Release this event.
+	void Release() override;
+
 	String type;
 	EventId id;
 	bool interruptible;
 	
 	bool interrupted;
 	bool interrupted_immediate;
-	EventPhase phase;
+	bool default_prevented;
 
 	bool has_mouse_position;
 	Vector2f mouse_screen_position;
 
+	EventPhase phase;
+
 	EventInstancer* instancer;
 
 	friend class Factory;

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

@@ -99,11 +99,11 @@ public:
 	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 {
+	T* get() const 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); }
+	T* operator->() const 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.

+ 17 - 0
Source/Core/Event.cpp

@@ -40,6 +40,7 @@ Event::Event() : id(EventId::Invalid)
 	interruptible = false;
 	interrupted = false;
 	interrupted_immediate = false;
+	default_prevented = false;
 	current_element = nullptr;
 	target_element = nullptr;
 	has_mouse_position = false;
@@ -52,6 +53,7 @@ Event::Event(Element* _target_element, EventId id, const String& type, const Dic
 	phase = EventPhase::None;
 	interrupted = false;
 	interrupted_immediate = false;
+	default_prevented = false;
 	current_element = nullptr;
 
 	has_mouse_position = false;
@@ -134,6 +136,16 @@ bool Event::IsImmediatePropagating() const
 	return !interrupted_immediate;
 }
 
+bool Event::IsDefaultPrevented() const
+{
+	return default_prevented;
+}
+
+bool Event::IsInterruptible() const
+{
+	return interruptible;
+}
+
 void Event::StopImmediatePropagation()
 {
 	if(interruptible)
@@ -143,6 +155,11 @@ void Event::StopImmediatePropagation()
 	}
 }
 
+void Event::PreventDefault()
+{
+	default_prevented = true;
+}
+
 const Dictionary& Event::GetParameters() const
 {
 	return parameters;

+ 56 - 50
Source/Core/EventDispatcher.cpp

@@ -109,46 +109,40 @@ void EventDispatcher::DetachAllEvents()
 }
 
 /*
-	EventListenersToExecute
+	CollectedListener
 
-	When dispatching an event we collect all possible event listeners and default actions to execute.
+	When dispatching an event we collect all possible event listeners to execute.
 	They are stored in observer pointers, so that we can safely check if they have been destroyed since the previous listener execution.
 */
+struct CollectedListener {
 
-struct EventListenersToExecute {
-
-	EventListenersToExecute(Element* _element, EventListener* _listener, int dom_distance_from_target, bool in_capture_phase) : element(_element->GetObserverPtr()), listener(_listener ? _listener->GetObserverPtr() : nullptr)
+	CollectedListener(Element* _element, EventListener* _listener, int dom_distance_from_target, bool in_capture_phase) : element(_element->GetObserverPtr()), listener(_listener->GetObserverPtr())
 	{
 		sort = dom_distance_from_target * (in_capture_phase ? -1 : 1);
-		if (!_listener)
-		{
-			// No listener means default action
-			sort += DefaultActionAdd;
-		}
 	}
 
-	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.
+	// Capture phase is given negative values.
 	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)); }
+	EventPhase GetPhase() const { return sort < 0 ? EventPhase::Capture : (sort == 0 ? EventPhase::Target : EventPhase::Bubble); }
 
-	bool operator<(const EventListenersToExecute& other) const {
+	bool operator<(const CollectedListener& other) const {
 		return sort < other.sort;
 	}
 };
 
 
-bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const String& type, const Dictionary& parameters, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase)
+bool EventDispatcher::DispatchEvent(Element* target_element, const EventId id, const String& type, const Dictionary& parameters, const bool interruptible, const bool bubbles, const DefaultActionPhase default_action_phase)
 {
-	std::vector<EventListenersToExecute> listeners;
+	RMLUI_ASSERTMSG(!((int)default_action_phase & (int)EventPhase::Capture), "We assume here that the default action phases cannot include capture phase.");
+
+	std::vector<CollectedListener> listeners;
+	std::vector<ObserverPtr<Element>> default_action_elements;
 
 	const EventPhase phases_to_execute = EventPhase((int)EventPhase::Capture | (int)EventPhase::Target | (bubbles ? (int)EventPhase::Bubble : 0));
 	
@@ -158,28 +152,37 @@ bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const S
 	while (walk_element)
 	{
 		EventDispatcher* dispatcher = walk_element->GetEventDispatcher();
-		dispatcher->CollectListeners(dom_distance_from_target, id, phases_to_execute, default_action_phase, listeners);
+		dispatcher->CollectListeners(dom_distance_from_target, id, phases_to_execute, listeners);
+
+		if(dom_distance_from_target == 0)
+		{
+			if ((int)default_action_phase & (int)EventPhase::Target)
+				default_action_elements.push_back(walk_element->GetObserverPtr());
+		}
+		else if((int)default_action_phase & (int)EventPhase::Bubble)
+		{
+			default_action_elements.push_back(walk_element->GetObserverPtr());
+		}
 
 		walk_element = walk_element->GetParentNode();
 		dom_distance_from_target += 1;
 	}
 
+	if (listeners.empty() && default_action_elements.empty())
+		return true;
+
 	// 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)
+	for (const auto& listener_desc : listeners)
 	{
 		Element* element = listener_desc.element.get();
 		EventListener* listener = listener_desc.listener.get();
@@ -190,25 +193,32 @@ bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const S
 			if (!event->IsPropagating())
 				break;
 			event->SetCurrentElement(element);
-			current_phase = listener_desc.GetPhase();
-			event->SetPhase(current_phase);
+			event->SetPhase(listener_desc.GetPhase());
 			previous_sort_value = listener_desc.sort;
 		}
 
+		// We only submit the event if both the current element and listener are still alive.
 		if (element && listener)
 		{
-			// We have a valid event listener
 			listener->ProcessEvent(*event);
 		}
-		else if (element && current_phase == EventPhase::None)
+
+		if (!event->IsImmediatePropagating())
+			break;
+	}
+
+	// Process the default actions.
+	for (auto& element_ptr : default_action_elements)
+	{
+		if (event->IsDefaultPrevented())
+			break;
+
+		if (Element* element = element_ptr.get())
 		{
-			// EventPhase::None means default actions. We assume default actions only execute in either target or bubble phase.
+			event->SetCurrentElement(element);
 			event->SetPhase(element == target_element ? EventPhase::Target : EventPhase::Bubble);
 			element->ProcessDefaultAction(*event);
 		}
-
-		if (!event->IsImmediatePropagating())
-			break;
 	}
 
 	bool propagating = event->IsPropagating();
@@ -217,38 +227,34 @@ bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const S
 }
 
 
-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)
+void EventDispatcher::CollectListeners(int dom_distance_from_target, const EventId event_id, const EventPhase event_executes_in_phases, std::vector<CollectedListener>& 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)
+	const bool in_target_phase = (dom_distance_from_target == 0);
+
+	if (in_target_phase)
 	{
-		if (in_target_phase)
+		// Listeners always attach to target phase, but make sure the event can actually execute in target phase.
+		if ((int)event_executes_in_phases & (int)EventPhase::Target)
 		{
-			// 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);
+			for (auto it = begin; it != end; ++it)
+				collect_listeners.emplace_back(element, it->listener, dom_distance_from_target, false);
 		}
-		else
+	}
+	else
+	{
+		// Iterate through all the listeners and collect those matching the event execution phase.
+		for (auto it = begin; it != end; ++it)
 		{
-			// Listeners will either attach to capture or bubble phase, make sure event can execute in the same phase.
+			// Listeners will either attach to capture or bubble phase, make sure the 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);
 }
 
 

+ 2 - 2
Source/Core/EventDispatcher.h

@@ -37,7 +37,7 @@ namespace Core {
 
 class Element;
 class EventListener;
-struct EventListenersToExecute;
+struct CollectedListener;
 
 struct EventListenerEntry {
 	EventListenerEntry(EventId id, EventListener* listener, bool in_capture_phase) : id(id), in_capture_phase(in_capture_phase), listener(listener) {}
@@ -103,7 +103,7 @@ private:
 	Listeners listeners;
 
 	// 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);
+	void CollectListeners(int dom_distance_from_target, EventId event_id, EventPhase phases_to_execute, std::vector<CollectedListener>& collect_listeners);
 };