Skip to content

[WIP] Introduce `OS.set_drag_mode()` - Tell the window manager to move / resize the window.

Borderless windows have a big disadvantage compared to normal windows — they can't be dragged (drug?) or resized without implementing a significant amount of custom logic. And even if you do implement said logic, you'll still lose features such as window snapping. This PR introduces OS.set_drag_mode(DragMode drag_mode) and related getters and accessors.

The available drag modes are:

DRAG_MODE_NONE, // Current / default behavior.
DRAG_MODE_MOVE, // Tell the window manager to move the window when the mouse is dragged.
DRAG_MODE_RESIZE_TOP, // Tells the window manager to resize the window from the top when the mouse is dragged.
DRAG_MODE_RESIZE_RIGHT, // etc.
DRAG_MODE_RESIZE_BOTTOM,
DRAG_MODE_RESIZE_LEFT,
DRAG_MODE_RESIZE_TOPLEFT,
DRAG_MODE_RESIZE_TOPRIGHT,
DRAG_MODE_RESIZE_BOTTOMRIGHT,
DRAG_MODE_RESIZE_BOTTOMLEFT

The drag modes may be set at any time. They will only take effect when the mouse is held down and starts moving in the window.

Example Usage:

To make a panel draggable, add this to the panel's script:

func _on_Panel_mouse_entered():
	OS.set_drag_mode(OS.DRAG_MODE_MOVE);

func _on_Panel_mouse_exited():
	OS.set_drag_mode(OS.DRAG_MODE_NONE);

This will treat the panel as a titlebar. Buttons or other interactable controls inside the panel will still be usable as normal without triggering a window move.

Enabling window resize on a background panel is a bit trickier. Say you have a root panel with 5 pixels of internal margin. Here's how you'd make it control window resize:

func _on_Root_Panel_gui_input( ev ):
	if ev is InputEventMouseMotion:
		var horizontal = -1;
		var vertical = -1;
		var mode = OS.DRAG_MODE_NONE;
		var cursor = Control.CURSOR_ARROW;

		if ev.position.x > OS.window_size.x - 5:
			horizontal = OS.DRAG_MODE_RESIZE_RIGHT;
			cursor = Control.CURSOR_HSIZE;
			mode = horizontal;

		if ev.position.x < 5:
			cursor = Control.CURSOR_HSIZE;
			horizontal = OS.DRAG_MODE_RESIZE_LEFT;
			mode = horizontal;

		if ev.position.y < 5:
			cursor = Control.CURSOR_VSIZE;
			vertical = OS.DRAG_MODE_RESIZE_TOP;
			mode = vertical;

		if ev.position.y > OS.window_size.y - 5:
			cursor = Control.CURSOR_VSIZE;
			vertical = OS.DRAG_MODE_RESIZE_BOTTOM;
			mode = vertical;

		if vertical == OS.DRAG_MODE_RESIZE_TOP && horizontal == OS.DRAG_MODE_RESIZE_RIGHT:
			mode = OS.DRAG_MODE_RESIZE_TOPRIGHT;
			cursor = Control.CURSOR_BDIAGSIZE;

		elif vertical == OS.DRAG_MODE_RESIZE_TOP && horizontal == OS.DRAG_MODE_RESIZE_LEFT:
			mode = OS.DRAG_MODE_RESIZE_TOPLEFT;
			cursor = Control.CURSOR_FDIAGSIZE;

		elif vertical == OS.DRAG_MODE_RESIZE_BOTTOM && horizontal == OS.DRAG_MODE_RESIZE_RIGHT:
			mode = OS.DRAG_MODE_RESIZE_BOTTOMRIGHT;
			cursor = Control.CURSOR_FDIAGSIZE;

		elif vertical == OS.DRAG_MODE_RESIZE_BOTTOM && horizontal == OS.DRAG_MODE_RESIZE_LEFT:
			mode = OS.DRAG_MODE_RESIZE_BOTTOMLEFT;
			cursor = Control.CURSOR_BDIAGSIZE;

		if OS.drag_mode != mode:
			self.mouse_default_cursor_shape = cursor;
			OS.set_drag_mode(mode);

Together, those scripts are sufficient to allow native window movement and resize support as shown in the GIF below (Pardon the recording quality, the jittering when moving isn't present in actual usage.)

ezgif com-optimize

Status:

  • X11 Implementation
  • macOS Implementation
  • Windows Implementation Doesn't support Aero Snap yet.
  • Haiku Implementation?
  • UWP Implementation? Doesn't appear to be possible.

Android, iPhone, and Web support shouldn't be necessary since they don't have a floating window manager system. (At least for the time being.)

I know I should really discuss the features for these PRs beforehand on IRC or Discord or something, but unfortunately, my timezone appears to be the opposite of pretty much everyone else, so it's faster for me to just write the code and PR it for discussion.

Merge request reports