Browse Source

Fix drag and drop between windows in X11 display server

Proper implementation for get_window_at_screen_position:
Now getting the topmost last active window when overlapping.

Mouse drag & release events:
They are now propagated through the current focused window, in order to
make it consistent with the engine expectations and the Windows display
server implementation.
PouleyKetchoupp 5 years ago
parent
commit
eeebe6914e
2 changed files with 86 additions and 15 deletions
  1. 84 15
      platform/linuxbsd/display_server_x11.cpp
  2. 2 0
      platform/linuxbsd/display_server_x11.h

+ 84 - 15
platform/linuxbsd/display_server_x11.cpp

@@ -742,15 +742,31 @@ ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) co
 }
 
 DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const {
-#warning This is an incorrect implementation, if windows overlap, it should return the topmost visible one or none if occluded by a foreign window
-
+	WindowID found_window = INVALID_WINDOW_ID;
+	WindowID parent_window = INVALID_WINDOW_ID;
+	unsigned int focus_order = 0;
 	for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
-		Rect2i win_rect = Rect2i(window_get_position(E->key()), window_get_size(E->key()));
+		const WindowData &wd = E->get();
+
+		// Discard windows with no focus.
+		if (wd.focus_order == 0) {
+			continue;
+		}
+
+		// Find topmost window which contains the given position.
+		WindowID window_id = E->key();
+		Rect2i win_rect = Rect2i(window_get_position(window_id), window_get_size(window_id));
 		if (win_rect.has_point(p_position)) {
-			return E->key();
+			// For siblings, pick the window which was focused last.
+			if ((parent_window != wd.transient_parent) || (wd.focus_order > focus_order)) {
+				found_window = window_id;
+				parent_window = wd.transient_parent;
+				focus_order = wd.focus_order;
+			}
 		}
 	}
-	return INVALID_WINDOW_ID;
+
+	return found_window;
 }
 
 void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window) {
@@ -2588,6 +2604,10 @@ void DisplayServerX11::process_events() {
 
 				wd.focused = true;
 
+				// Keep track of focus order for overlapping windows.
+				static unsigned int focus_order = 0;
+				wd.focus_order = ++focus_order;
+
 				_send_window_event(wd, WINDOW_EVENT_FOCUS_IN);
 
 				if (mouse_mode_grab) {
@@ -2707,11 +2727,11 @@ void DisplayServerX11::process_events() {
 
 				mb->set_pressed((event.type == ButtonPress));
 
+				const WindowData &wd = windows[window_id];
+
 				if (event.type == ButtonPress) {
 					DEBUG_LOG_X11("[%u] ButtonPress window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index());
 
-					const WindowData &wd = windows[window_id];
-
 					// Ensure window focus on click.
 					// RevertToPointerRoot is used to make sure we don't lose all focus in case
 					// a subwindow and its parent are both destroyed.
@@ -2739,6 +2759,31 @@ void DisplayServerX11::process_events() {
 					}
 				} else {
 					DEBUG_LOG_X11("[%u] ButtonRelease window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index());
+
+					if (!wd.focused) {
+						// Propagate the event to the focused window,
+						// because it's received only on the topmost window.
+						// Note: This is needed for drag & drop to work between windows,
+						// because the engine expects events to keep being processed
+						// on the same window dragging started.
+						for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+							const WindowData &wd_other = E->get();
+							WindowID window_id_other = E->key();
+							if (wd_other.focused) {
+								if (window_id_other != window_id) {
+									int x, y;
+									Window child;
+									XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xbutton.x, event.xbutton.y, &x, &y, &child);
+
+									mb->set_window_id(window_id_other);
+									mb->set_position(Vector2(x, y));
+									mb->set_global_position(mb->get_position());
+									Input::get_singleton()->accumulate_input_event(mb);
+								}
+								break;
+							}
+						}
+					}
 				}
 
 				Input::get_singleton()->accumulate_input_event(mb);
@@ -2788,6 +2833,9 @@ void DisplayServerX11::process_events() {
 					break;
 				}
 
+				const WindowData &wd = windows[window_id];
+				bool focused = wd.focused;
+
 				if (mouse_mode == MOUSE_MODE_CAPTURED) {
 					if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) {
 						break;
@@ -2796,7 +2844,7 @@ void DisplayServerX11::process_events() {
 					Point2i new_center = pos;
 					pos = last_mouse_pos + xi.relative_motion;
 					center = new_center;
-					do_mouse_warp = windows[window_id].focused; // warp the cursor if we're focused in
+					do_mouse_warp = focused; // warp the cursor if we're focused in
 				}
 
 				if (!last_mouse_pos_valid) {
@@ -2838,14 +2886,11 @@ void DisplayServerX11::process_events() {
 				}
 				mm->set_tilt(xi.tilt);
 
-				// Make the absolute position integral so it doesn't look _too_ weird :)
-				Point2i posi(pos);
-
 				_get_key_modifier_state(event.xmotion.state, mm);
 				mm->set_button_mask(mouse_get_button_state());
-				mm->set_position(posi);
-				mm->set_global_position(posi);
-				Input::get_singleton()->set_mouse_position(posi);
+				mm->set_position(pos);
+				mm->set_global_position(pos);
+				Input::get_singleton()->set_mouse_position(pos);
 				mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
 
 				mm->set_relative(rel);
@@ -2856,8 +2901,32 @@ void DisplayServerX11::process_events() {
 				// Don't propagate the motion event unless we have focus
 				// this is so that the relative motion doesn't get messed up
 				// after we regain focus.
-				if (windows[window_id].focused || !mouse_mode_grab) {
+				if (focused) {
 					Input::get_singleton()->accumulate_input_event(mm);
+				} else {
+					// Propagate the event to the focused window,
+					// because it's received only on the topmost window.
+					// Note: This is needed for drag & drop to work between windows,
+					// because the engine expects events to keep being processed
+					// on the same window dragging started.
+					for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {
+						const WindowData &wd_other = E->get();
+						if (wd_other.focused) {
+							int x, y;
+							Window child;
+							XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xmotion.x, event.xmotion.y, &x, &y, &child);
+
+							Point2i pos_focused(x, y);
+
+							mm->set_window_id(E->key());
+							mm->set_position(pos_focused);
+							mm->set_global_position(pos_focused);
+							mm->set_speed(Input::get_singleton()->get_last_mouse_speed());
+							Input::get_singleton()->accumulate_input_event(mm);
+
+							break;
+						}
+					}
 				}
 
 			} break;

+ 2 - 0
platform/linuxbsd/display_server_x11.h

@@ -144,6 +144,8 @@ class DisplayServerX11 : public DisplayServer {
 		Vector2i last_position_before_fs;
 		bool focused = false;
 		bool minimized = false;
+
+		unsigned int focus_order = 0;
 	};
 
 	Map<WindowID, WindowData> windows;