start_drag() function on Window not working

Godot Version

Godot 4.7.2

Question

Hello! I’m trying to make a desktop companion game, similar to something like Desktop Defender or Ropuka’s Idle Island. I want the player to be able to right-click anywhere inside the window to start dragging it, but calling the built-in start_drag() function doesn’t seem to work. Here is my code thus far:

class_name WindowManager extends Node

var window: Window

func _ready() -> void:
	window = get_window()

func _input(event: InputEvent) -> void:
	if event.is_action_pressed("right_click"):
		window.start_drag()

This script is attached to a default node inside my main scene. I’ve confirmed that the input is received, since replacing start_drag() with print(“drag”) will print a message. Also, sometimes the cursor will turn into a cross arrow for a split second, but this is inconsistent.

I’m on Linux Mint, is this an OS issue? Or is there something else I’m missing?

I programmed a drag and drop system for the card game 24 a few weeks ago. The code is here on Github.

Drag and drop systems can get complicated, depending on what features you want it to have. What happens on a drop? Where does the drop happen? What other inputs are allowed?

Essentially you need to do two things. 1) Store the dragged object at the start (in my code _start_drag stores the reference to the dragged object as drag_obj). And 2) you need to update the position of the dragged object according to the (mouse) pointer.

Something like this should do it:

func _process(delta : float):
	if drag_obj:
		drag_obj.global_position = get_global_mouse_position() - (drag_obj.size / 2)

As far as I can tell, the left mouse button must be pressed for it to work.

Also, if you have game embedding enabled it won’t work so you’ll need to disable it.

I ended up writing my own solution. This approach should be more flexible anyway.

class_name WindowManager extends Node

var window: Window
var screen_rect: Rect2i

var is_dragging: bool = false
var mouse_offset: Vector2i = Vector2i.ZERO

func _ready() -> void:
	window = get_window()
	screen_rect = DisplayServer.screen_get_usable_rect()

func _input(event: InputEvent) -> void:
	if event.is_action_pressed("right_click"):
		mouse_offset = window.get_mouse_position()
		is_dragging = true
		Input.set_default_cursor_shape(Input.CURSOR_DRAG)
	elif event.is_action_released("right_click"):
		is_dragging = false
		Input.set_default_cursor_shape(Input.CURSOR_ARROW)
	elif event is InputEventMouseMotion and is_dragging:
		var true_mouse_pos := DisplayServer.mouse_get_position()
		window.position = true_mouse_pos - mouse_offset
		
		window.position.x = clamp(window.position.x, screen_rect.position.x, screen_rect.end.x - window.size.x)
		window.position.y = clamp(window.position.y, screen_rect.position.y, screen_rect.end.y - window.size.y)

Looks very good to me. One thing that is particularly new to me is that you additionally detect for InputEventMouseMotion in your input. In my code I just assumed that between press and release the drag is being continued without continuously checking for motion and updating the position of the dragged object in the _process() function instead.

Your solution has the advantage that the code that handles the drag is all in one function, but also the order of the if statements matter, which one should not forget.

Your style with InputEventMouseMotion can also be found in the Godot docs as an input example, so I would as a default it is cleaner not to use the process function for the drag.