Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Client side decorations] Implement window drag handles. #80891

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1421,13 +1421,32 @@
[b]Note:[/b] [method warp_mouse] is only supported on Windows, macOS, and Linux (X11/Wayland). It has no effect on Android, iOS, and Web.
</description>
</method>
<method name="window_add_decoration">
<return type="int" />
<param index="0" name="region" type="PackedVector2Array" />
<param index="1" name="dec_type" type="int" enum="DisplayServer.WindowDecorationType" />
<param index="2" name="window" type="int" default="0" />
<description>
Adds polygon which should act as window client side decoration (UI element used to move or resize window) specified by [param dec_type]. Returns decoration ID.
</description>
</method>
<method name="window_can_draw" qualifiers="const">
<return type="bool" />
<param index="0" name="window_id" type="int" default="0" />
<description>
Returns [code]true[/code] if anything can be drawn in the window specified by [param window_id], [code]false[/code] otherwise. Using the [code]--disable-render-loop[/code] command line argument or a headless build will return [code]false[/code].
</description>
</method>
<method name="window_change_decoration">
<return type="void" />
<param index="0" name="id" type="int" />
<param index="1" name="region" type="PackedVector2Array" />
<param index="2" name="dec_type" type="int" enum="DisplayServer.WindowDecorationType" />
<param index="3" name="window" type="int" default="0" />
<description>
Changes type and polygon of the client side decoration (UI element used to move or resize window) specified by [param id].
</description>
</method>
<method name="window_get_active_popup" qualifiers="const">
<return type="int" />
<description>
Expand All @@ -1448,6 +1467,13 @@
Returns the screen the window specified by [param window_id] is currently positioned on. If the screen overlaps multiple displays, the screen where the window's center is located is returned. See also [method window_set_current_screen].
</description>
</method>
<method name="window_get_decorations" qualifiers="const">
<return type="Array" />
<param index="0" name="window" type="int" default="0" />
<description>
Returns [Array] of the window client side decorations (UI elements used to move or resize window) set by [method window_add_decoration] and [method window_change_decoration], each entry is a [Dictionary] with the following keys: [code]id: Int[/code], [code]region: PackedVector2Array[/code] and [code]type: WindowDecorationType[/code].
</description>
</method>
<method name="window_get_flag" qualifiers="const">
<return type="bool" />
<param index="0" name="flag" type="int" enum="DisplayServer.WindowFlags" />
Expand Down Expand Up @@ -1579,6 +1605,14 @@
Moves the window specified by [param window_id] to the foreground, so that it is visible over other windows.
</description>
</method>
<method name="window_remove_decoration">
<return type="void" />
<param index="0" name="id" type="int" />
<param index="1" name="window" type="int" default="0" />
<description>
Removes client side decoration (UI element used to move or resize window) specified by [param id].
</description>
</method>
<method name="window_request_attention">
<return type="void" />
<param index="0" name="window_id" type="int" default="0" />
Expand Down Expand Up @@ -1895,6 +1929,9 @@
<constant name="FEATURE_NATIVE_DIALOG_FILE_EXTRA" value="26" enum="Feature">
The display server supports all features of [constant FEATURE_NATIVE_DIALOG_FILE], with the added functionality of Options and native dialog file access to [code]res://[/code] and [code]user://[/code] paths. See [method file_dialog_show] and [method file_dialog_with_options_show]. [b]Windows, macOS, Linux (X11/Wayland)[/b]
</constant>
<constant name="FEATURE_CLIENT_SIDE_DECORATIONS" value="27" enum="Feature">
Display server supports client side decorations. See [method window_add_decoration], [method window_change_decoration], and [method window_remove_decoration].
</constant>
<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
Makes the mouse cursor visible if it is hidden.
</constant>
Expand Down Expand Up @@ -2140,6 +2177,36 @@
Sent when the window title bar decoration is changed (e.g. [constant WINDOW_FLAG_EXTEND_TO_TITLE] is set or window entered/exited full screen mode).
[b]Note:[/b] This flag is implemented only on macOS.
</constant>
<constant name="WINDOW_DECORATION_TOP_LEFT" value="0" enum="WindowDecorationType">
Window top-left resize element.
</constant>
<constant name="WINDOW_DECORATION_TOP" value="1" enum="WindowDecorationType">
Window top resize element.
</constant>
<constant name="WINDOW_DECORATION_TOP_RIGHT" value="2" enum="WindowDecorationType">
Window top-right resize element.
</constant>
<constant name="WINDOW_DECORATION_LEFT" value="3" enum="WindowDecorationType">
Window left resize element.
</constant>
<constant name="WINDOW_DECORATION_RIGHT" value="4" enum="WindowDecorationType">
Window right resize element.
</constant>
<constant name="WINDOW_DECORATION_BOTTOM_LEFT" value="5" enum="WindowDecorationType">
Window bottom-left resize element.
</constant>
<constant name="WINDOW_DECORATION_BOTTOM" value="6" enum="WindowDecorationType">
Window bottom resize element.
</constant>
<constant name="WINDOW_DECORATION_BOTTOM_RIGHT" value="7" enum="WindowDecorationType">
Window bottom-right resize element.
</constant>
<constant name="WINDOW_DECORATION_MOVE" value="8" enum="WindowDecorationType">
Window move element.
</constant>
<constant name="WINDOW_DECORATION_PASS" value="9" enum="WindowDecorationType">
Element excluded from window decorations.
</constant>
<constant name="VSYNC_DISABLED" value="0" enum="VSyncMode">
No vertical synchronization, which means the engine will display frames as fast as possible (tearing may be visible). Framerate is unlimited (regardless of [member Engine.max_fps]).
</constant>
Expand Down
24 changes: 24 additions & 0 deletions doc/classes/WindowDecoration.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="WindowDecoration" inherits="Control" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A move / resize region for the native [Window].
</brief_description>
<description>
A move / resize region, used to implement client side decorations for the native [Window].
</description>
<tutorials>
</tutorials>
<members>
<member name="decoration_type" type="int" setter="set_decoration_type" getter="get_decoration_type" enum="DisplayServer.WindowDecorationType" default="8">
Move / resize region type.
</member>
<member name="non_rectangular_region" type="bool" setter="set_non_rectangular_region" getter="is_non_rectangular_region" default="false">
If [code]true[/code], polygonal region is used instead of control bounds.
</member>
<member name="polygon" type="PackedVector2Array" setter="set_polygon" getter="get_polygon" default="PackedVector2Array()">
The region's list of vertices. The final point will be connected to the first.
[b]Note:[/b] Used only if [member non_rectangular_region] is [code]true[/code].
[b]Note:[/b] This returns a copy of the [PackedVector2Array] rather than a reference.
</member>
</members>
</class>
25 changes: 18 additions & 7 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1245,15 +1245,23 @@ void EditorNode::_viewport_resized() {
void EditorNode::_titlebar_resized() {
DisplayServer::get_singleton()->window_set_window_buttons_offset(Vector2i(title_bar->get_global_position().y + title_bar->get_size().y / 2, title_bar->get_global_position().y + title_bar->get_size().y / 2), DisplayServer::MAIN_WINDOW_ID);
const Vector3i &margin = DisplayServer::get_singleton()->window_get_safe_title_margins(DisplayServer::MAIN_WINDOW_ID);
int left_sp = main_menu->get_minimum_size().x;
int right_sp = project_run_bar->get_minimum_size().x + right_menu_hb->get_minimum_size().x;
if (left_menu_spacer) {
int w = (gui_base->is_layout_rtl()) ? margin.y : margin.x;
left_menu_spacer->set_custom_minimum_size(Size2(w, 0));
left_sp += w;
}
if (right_menu_spacer) {
int w = (gui_base->is_layout_rtl()) ? margin.x : margin.y;
right_menu_spacer->set_custom_minimum_size(Size2(w, 0));
right_sp += w;
}
if (title_bar) {
// Adjust spacers to center buttons.
left_spacer_al->set_custom_minimum_size(Size2(MAX(0, right_sp - left_sp), 0));
right_spacer_al->set_custom_minimum_size(Size2(MAX(0, left_sp - right_sp), 0));

title_bar->set_custom_minimum_size(Size2(0, margin.z - title_bar->get_global_position().y));
}
}
Expand Down Expand Up @@ -7338,6 +7346,10 @@ EditorNode::EditorNode() {
left_spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
title_bar->add_child(left_spacer);

left_spacer_al = memnew(Control);
left_spacer_al->set_mouse_filter(Control::MOUSE_FILTER_PASS);
title_bar->add_child(left_spacer_al);

if (can_expand && global_menu) {
project_title = memnew(Label);
project_title->add_theme_font_override(SceneStringName(font), theme->get_font(SNAME("bold"), EditorStringName(EditorFonts)));
Expand All @@ -7350,7 +7362,7 @@ EditorNode::EditorNode() {
left_spacer->add_child(project_title);
}

HBoxContainer *main_editor_button_hb = memnew(HBoxContainer);
main_editor_button_hb = memnew(HBoxContainer);
main_editor_button_hb->set_mouse_filter(Control::MOUSE_FILTER_STOP);
editor_main_screen->set_button_container(main_editor_button_hb);
title_bar->add_child(main_editor_button_hb);
Expand Down Expand Up @@ -7446,13 +7458,17 @@ EditorNode::EditorNode() {
right_spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
title_bar->add_child(right_spacer);

right_spacer_al = memnew(Control);
right_spacer_al->set_mouse_filter(Control::MOUSE_FILTER_PASS);
title_bar->add_child(right_spacer_al);

project_run_bar = memnew(EditorRunBar);
project_run_bar->set_mouse_filter(Control::MOUSE_FILTER_STOP);
title_bar->add_child(project_run_bar);
project_run_bar->connect("play_pressed", callable_mp(this, &EditorNode::_project_run_started));
project_run_bar->connect("stop_pressed", callable_mp(this, &EditorNode::_project_run_stopped));

HBoxContainer *right_menu_hb = memnew(HBoxContainer);
right_menu_hb = memnew(HBoxContainer);
right_menu_hb->set_mouse_filter(Control::MOUSE_FILTER_STOP);
title_bar->add_child(right_menu_hb);

Expand Down Expand Up @@ -7944,11 +7960,6 @@ EditorNode::EditorNode() {
add_child(screenshot_timer);
screenshot_timer->set_owner(get_owner());

// Adjust spacers to center 2D / 3D / Script buttons.
int max_w = MAX(project_run_bar->get_minimum_size().x + right_menu_hb->get_minimum_size().x, main_menu->get_minimum_size().x);
left_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - main_menu->get_minimum_size().x), 0));
right_spacer->set_custom_minimum_size(Size2(MAX(0, max_w - project_run_bar->get_minimum_size().x - right_menu_hb->get_minimum_size().x), 0));

// Extend menu bar to window title.
if (can_expand) {
DisplayServer::get_singleton()->process_events();
Expand Down
5 changes: 5 additions & 0 deletions editor/editor_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class ColorPicker;
class ConfirmationDialog;
class Control;
class FileDialog;
class HBoxContainer;
class MenuBar;
class MenuButton;
class OptionButton;
Expand Down Expand Up @@ -318,7 +319,11 @@ class EditorNode : public Node {

Label *project_title = nullptr;
Control *left_menu_spacer = nullptr;
Control *left_spacer_al = nullptr;
Control *right_menu_spacer = nullptr;
Control *right_spacer_al = nullptr;
HBoxContainer *main_editor_button_hb = nullptr;
HBoxContainer *right_menu_hb = nullptr;
EditorTitleBar *title_bar = nullptr;
EditorRunBar *project_run_bar = nullptr;
MenuBar *main_menu = nullptr;
Expand Down
146 changes: 110 additions & 36 deletions editor/gui/editor_title_bar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,55 +30,129 @@

#include "editor_title_bar.h"

void EditorTitleBar::gui_input(const Ref<InputEvent> &p_event) {
if (!can_move) {
void EditorTitleBar::_update_rects() {
if (!is_inside_tree()) {
return;
}
if (!DisplayServer::get_singleton()) {
return;
}
if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIENT_SIDE_DECORATIONS)) {
return;
}

DisplayServer::WindowID wid = get_viewport()->get_window_id();
for (int &id : ids) {
DisplayServer::get_singleton()->window_remove_decoration(id, wid);
}
ids.clear();

if (can_move) {
Vector<Rect2i> rects;
int prev_pos = 0;
int count = get_child_count();
for (int i = 0; i < count; i++) {
Control *n = Object::cast_to<Control>(get_child(i));
if (n && n->get_mouse_filter() != Control::MOUSE_FILTER_PASS) {
int start = n->get_position().x;
rects.push_back(Rect2(prev_pos, 0, start - prev_pos, get_size().y));
prev_pos = start + n->get_size().x;
}
}
if (prev_pos != 0) {
rects.push_back(Rect2(prev_pos, 0, get_size().x - prev_pos, get_size().y));
}

Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid() && moving) {
if (mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
Window *w = Object::cast_to<Window>(get_viewport());
if (w) {
Point2 mouse = DisplayServer::get_singleton()->mouse_get_position();
w->set_position(mouse - click_pos);
for (Rect2i &rect : rects) {
Vector<Point2> polygon_global;
polygon_global.push_back(rect.position);
polygon_global.push_back(rect.position + Vector2(rect.size.x, 0));
polygon_global.push_back(rect.position + rect.size);
polygon_global.push_back(rect.position + Vector2(0, rect.size.y));

Transform2D t = get_global_transform();
for (Vector2 &E : polygon_global) {
E = t.xform(E);
}
} else {
moving = false;
int id = DisplayServer::get_singleton()->window_add_decoration(polygon_global, DisplayServer::WINDOW_DECORATION_MOVE, wid);
ids.push_back(id);
}
}
}

void EditorTitleBar::_global_transform_changed() {
_update_rects();
}

void EditorTitleBar::add_child_notify(Node *p_child) {
Control::add_child_notify(p_child);

Control *control = Object::cast_to<Control>(p_child);
if (!control) {
return;
}

Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && has_point(mb->get_position())) {
Window *w = Object::cast_to<Window>(get_viewport());
if (w) {
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
click_pos = DisplayServer::get_singleton()->mouse_get_position() - w->get_position();
moving = true;
} else {
moving = false;
}
control->connect(SceneStringName(item_rect_changed), callable_mp(this, &EditorTitleBar::_update_rects));

_update_rects();
}

void EditorTitleBar::move_child_notify(Node *p_child) {
Control::move_child_notify(p_child);

if (!Object::cast_to<Control>(p_child)) {
return;
}

_update_rects();
}

void EditorTitleBar::remove_child_notify(Node *p_child) {
Control::remove_child_notify(p_child);

Control *control = Object::cast_to<Control>(p_child);
if (!control) {
return;
}

control->disconnect(SceneStringName(item_rect_changed), callable_mp(this, &EditorTitleBar::_update_rects));

_update_rects();
}

void EditorTitleBar::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_RESIZED: {
_update_rects();
} break;

case NOTIFICATION_ENTER_TREE: {
get_viewport()->connect("size_changed", callable_mp(this, &EditorTitleBar::_update_rects));
_update_rects();
} break;

case NOTIFICATION_EXIT_TREE: {
get_viewport()->disconnect("size_changed", callable_mp(this, &EditorTitleBar::_update_rects));
if (!DisplayServer::get_singleton()) {
return;
}
if (mb->get_button_index() == MouseButton::LEFT && mb->is_double_click() && mb->is_pressed()) {
if (DisplayServer::get_singleton()->window_maximize_on_title_dbl_click()) {
if (w->get_mode() == Window::MODE_WINDOWED) {
w->set_mode(Window::MODE_MAXIMIZED);
} else if (w->get_mode() == Window::MODE_MAXIMIZED) {
w->set_mode(Window::MODE_WINDOWED);
}
} else if (DisplayServer::get_singleton()->window_minimize_on_title_dbl_click()) {
w->set_mode(Window::MODE_MINIMIZED);
}
moving = false;
if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIENT_SIDE_DECORATIONS)) {
return;
}
}
DisplayServer::WindowID wid = get_viewport()->get_window_id();
for (int &id : ids) {
DisplayServer::get_singleton()->window_remove_decoration(id, wid);
}
ids.clear();
} break;
}
}

void EditorTitleBar::set_can_move_window(bool p_enabled) {
can_move = p_enabled;
set_process_input(can_move);
if (can_move != p_enabled) {
can_move = p_enabled;
_update_rects();
}
}

bool EditorTitleBar::get_can_move_window() const {
Expand Down
Loading