Skip to content

Commit

Permalink
[Client side decorations] Implement window drag handles.
Browse files Browse the repository at this point in the history
  • Loading branch information
bruvzg committed Nov 24, 2024
1 parent 0c45ace commit f8765c0
Show file tree
Hide file tree
Showing 42 changed files with 1,459 additions and 87 deletions.
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

0 comments on commit f8765c0

Please sign in to comment.