Browse Source

Add mouse_shape_entered and mouse_shape_exited signals to CollisionObject2D.

Co-authored-by: Hugo Locurcio <[email protected]>
sygi 4 years ago
parent
commit
6f3e7f7cb0

+ 4 - 1
core/templates/pair.h

@@ -60,7 +60,10 @@ bool operator!=(const Pair<F, S> &pair, const Pair<F, S> &other) {
 template <class F, class S>
 struct PairSort {
 	bool operator()(const Pair<F, S> &A, const Pair<F, S> &B) const {
-		return A.first < B.first;
+		if (A.first != B.first) {
+			return A.first < B.first;
+		}
+		return A.second < B.second;
 	}
 };
 

+ 16 - 2
doc/classes/CollisionObject2D.xml

@@ -234,12 +234,26 @@
 		</signal>
 		<signal name="mouse_entered">
 			<description>
-				Emitted when the mouse pointer enters any of this object's shapes. Requires [member input_pickable] to be [code]true[/code] and at least one [code]collision_layer[/code] bit to be set.
+				Emitted when the mouse pointer enters any of this object's shapes. Requires [member input_pickable] to be [code]true[/code] and at least one [member collision_layer] bit to be set. Note that moving between different shapes within a single [CollisionObject2D] won't cause this signal to be emitted.
 			</description>
 		</signal>
 		<signal name="mouse_exited">
 			<description>
-				Emitted when the mouse pointer exits all this object's shapes. Requires [member input_pickable] to be [code]true[/code] and at least one [code]collision_layer[/code] bit to be set.
+				Emitted when the mouse pointer exits all this object's shapes. Requires [member input_pickable] to be [code]true[/code] and at least one [member collision_layer] bit to be set. Note that moving between different shapes within a single [CollisionObject2D] won't cause this signal to be emitted.
+			</description>
+		</signal>
+		<signal name="mouse_shape_entered">
+			<argument index="0" name="shape_idx" type="int">
+			</argument>
+			<description>
+				Emitted when the mouse pointer enters any of this object's shapes or moves from one shape to another. [code]shape_idx[/code] is the child index of the newly entered [Shape2D]. Requires [member input_pickable] to be [code]true[/code] and at least one [member collision_layer] bit to be set.
+			</description>
+		</signal>
+		<signal name="mouse_shape_exited">
+			<argument index="0" name="shape_idx" type="int">
+			</argument>
+			<description>
+				Emitted when the mouse pointer exits any of this object's shapes. [code]shape_idx[/code] is the child index of the exited [Shape2D]. Requires [member input_pickable] to be [code]true[/code] and at least one [member collision_layer] bit to be set.
 			</description>
 		</signal>
 	</signals>

+ 16 - 0
scene/2d/collision_object_2d.cpp

@@ -346,6 +346,20 @@ void CollisionObject2D::_mouse_exit() {
 	emit_signal(SceneStringNames::get_singleton()->mouse_exited);
 }
 
+void CollisionObject2D::_mouse_shape_enter(int p_shape) {
+	if (get_script_instance()) {
+		get_script_instance()->call(SceneStringNames::get_singleton()->_mouse_shape_enter, p_shape);
+	}
+	emit_signal(SceneStringNames::get_singleton()->mouse_shape_entered, p_shape);
+}
+
+void CollisionObject2D::_mouse_shape_exit(int p_shape) {
+	if (get_script_instance()) {
+		get_script_instance()->call(SceneStringNames::get_singleton()->_mouse_shape_exit, p_shape);
+	}
+	emit_signal(SceneStringNames::get_singleton()->mouse_shape_exited, p_shape);
+}
+
 void CollisionObject2D::set_only_update_transform_changes(bool p_enable) {
 	only_update_transform_changes = p_enable;
 }
@@ -406,6 +420,8 @@ void CollisionObject2D::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("input_event", PropertyInfo(Variant::OBJECT, "viewport", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::INT, "shape_idx")));
 	ADD_SIGNAL(MethodInfo("mouse_entered"));
 	ADD_SIGNAL(MethodInfo("mouse_exited"));
+	ADD_SIGNAL(MethodInfo("mouse_shape_entered", PropertyInfo(Variant::INT, "shape_idx")));
+	ADD_SIGNAL(MethodInfo("mouse_shape_exited", PropertyInfo(Variant::INT, "shape_idx")));
 
 	ADD_GROUP("Pickable", "input_");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "input_pickable"), "set_pickable", "is_pickable");

+ 3 - 0
scene/2d/collision_object_2d.h

@@ -73,6 +73,9 @@ protected:
 	void _mouse_enter();
 	void _mouse_exit();
 
+	void _mouse_shape_enter(int p_shape);
+	void _mouse_shape_exit(int p_shape);
+
 	void set_only_update_transform_changes(bool p_enable);
 
 public:

+ 53 - 31
scene/main/viewport.cpp

@@ -724,7 +724,6 @@ void Viewport::_process_picking() {
 							bool send_event = true;
 							if (is_mouse) {
 								Map<ObjectID, uint64_t>::Element *F = physics_2d_mouseover.find(res[i].collider_id);
-
 								if (!F) {
 									physics_2d_mouseover.insert(res[i].collider_id, frame);
 									co->_mouse_enter();
@@ -735,6 +734,13 @@ void Viewport::_process_picking() {
 										send_event = false;
 									}
 								}
+								Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *SF = physics_2d_shape_mouseover.find(Pair(res[i].collider_id, res[i].shape));
+								if (!SF) {
+									physics_2d_shape_mouseover.insert(Pair(res[i].collider_id, res[i].shape), frame);
+									co->_mouse_shape_enter(res[i].shape);
+								} else {
+									SF->get() = frame;
+								}
 							}
 
 							if (send_event) {
@@ -746,25 +752,7 @@ void Viewport::_process_picking() {
 			}
 
 			if (is_mouse) {
-				List<Map<ObjectID, uint64_t>::Element *> to_erase;
-
-				for (Map<ObjectID, uint64_t>::Element *E = physics_2d_mouseover.front(); E; E = E->next()) {
-					if (E->get() != frame) {
-						Object *o = ObjectDB::get_instance(E->key());
-						if (o) {
-							CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o);
-							if (co) {
-								co->_mouse_exit();
-							}
-						}
-						to_erase.push_back(E);
-					}
-				}
-
-				while (to_erase.size()) {
-					physics_2d_mouseover.erase(to_erase.front()->get());
-					to_erase.pop_front();
-				}
+				_cleanup_mouseover_colliders(false, false, frame);
 			}
 		}
 
@@ -2607,20 +2595,41 @@ void Viewport::_drop_mouse_focus() {
 void Viewport::_drop_physics_mouseover(bool p_paused_only) {
 	physics_has_last_mousepos = false;
 
+	_cleanup_mouseover_colliders(true, p_paused_only);
+
+#ifndef _3D_DISABLED
+	if (physics_object_over.is_valid()) {
+		CollisionObject3D *co = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(physics_object_over));
+		if (co) {
+			if (!(p_paused_only && co->can_process())) {
+				co->_mouse_exit();
+				physics_object_over = ObjectID();
+				physics_object_capture = ObjectID();
+			}
+		}
+	}
+#endif
+}
+
+void Viewport::_cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paused_only, uint64_t p_frame_reference) {
 	List<Map<ObjectID, uint64_t>::Element *> to_erase;
 
 	for (Map<ObjectID, uint64_t>::Element *E = physics_2d_mouseover.front(); E; E = E->next()) {
+		if (!p_clean_all_frames && E->get() == p_frame_reference) {
+			continue;
+		}
+
 		Object *o = ObjectDB::get_instance(E->key());
 		if (o) {
 			CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o);
 			if (co) {
-				if (p_paused_only && co->can_process()) {
+				if (p_clean_all_frames && p_paused_only && co->can_process()) {
 					continue;
 				}
 				co->_mouse_exit();
-				to_erase.push_back(E);
 			}
 		}
+		to_erase.push_back(E);
 	}
 
 	while (to_erase.size()) {
@@ -2628,18 +2637,31 @@ void Viewport::_drop_physics_mouseover(bool p_paused_only) {
 		to_erase.pop_front();
 	}
 
-#ifndef _3D_DISABLED
-	if (physics_object_over.is_valid()) {
-		CollisionObject3D *co = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(physics_object_over));
-		if (co) {
-			if (!(p_paused_only && co->can_process())) {
-				co->_mouse_exit();
-				physics_object_over = ObjectID();
-				physics_object_capture = ObjectID();
+	// Per-shape
+	List<Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *> shapes_to_erase;
+
+	for (Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *E = physics_2d_shape_mouseover.front(); E; E = E->next()) {
+		if (!p_clean_all_frames && E->get() == p_frame_reference) {
+			continue;
+		}
+
+		Object *o = ObjectDB::get_instance(E->key().first);
+		if (o) {
+			CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o);
+			if (co) {
+				if (p_clean_all_frames && p_paused_only && co->can_process()) {
+					continue;
+				}
+				co->_mouse_shape_exit(E->key().second);
 			}
 		}
+		shapes_to_erase.push_back(E);
+	}
+
+	while (shapes_to_erase.size()) {
+		physics_2d_shape_mouseover.erase(shapes_to_erase.front()->get());
+		shapes_to_erase.pop_front();
 	}
-#endif
 }
 
 Control *Viewport::_gui_get_focus_owner() {

+ 6 - 0
scene/main/viewport.h

@@ -32,6 +32,7 @@
 #define VIEWPORT_H
 
 #include "core/math/transform_2d.h"
+#include "core/templates/pair.h"
 #include "scene/main/node.h"
 #include "scene/resources/texture.h"
 #include "scene/resources/world_2d.h"
@@ -271,7 +272,12 @@ private:
 	bool handle_input_locally = true;
 	bool local_input_handled = false;
 
+	// Collider to frame
 	Map<ObjectID, uint64_t> physics_2d_mouseover;
+	// Collider & shape to frame
+	Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>> physics_2d_shape_mouseover;
+	// Cleans up colliders corresponding to old frames or all of them.
+	void _cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paused_only, uint64_t p_frame_reference = 0);
 
 	Ref<World2D> world_2d;
 	Ref<World3D> world_3d;

+ 4 - 0
scene/scene_string_names.cpp

@@ -66,6 +66,8 @@ SceneStringNames::SceneStringNames() {
 
 	mouse_entered = StaticCString::create("mouse_entered");
 	mouse_exited = StaticCString::create("mouse_exited");
+	mouse_shape_entered = StaticCString::create("mouse_shape_entered");
+	mouse_shape_exited = StaticCString::create("mouse_shape_exited");
 
 	focus_entered = StaticCString::create("focus_entered");
 	focus_exited = StaticCString::create("focus_exited");
@@ -170,6 +172,8 @@ SceneStringNames::SceneStringNames() {
 
 	_mouse_enter = StaticCString::create("_mouse_enter");
 	_mouse_exit = StaticCString::create("_mouse_exit");
+	_mouse_shape_enter = StaticCString::create("_mouse_shape_enter");
+	_mouse_shape_exit = StaticCString::create("_mouse_shape_exit");
 
 	_pressed = StaticCString::create("_pressed");
 	_toggled = StaticCString::create("_toggled");

+ 4 - 0
scene/scene_string_names.h

@@ -84,6 +84,8 @@ public:
 
 	StringName mouse_entered;
 	StringName mouse_exited;
+	StringName mouse_shape_entered;
+	StringName mouse_shape_exited;
 	StringName focus_entered;
 	StringName focus_exited;
 
@@ -182,6 +184,8 @@ public:
 
 	StringName _mouse_enter;
 	StringName _mouse_exit;
+	StringName _mouse_shape_enter;
+	StringName _mouse_shape_exit;
 
 	StringName frame_changed;