diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index e2a352de9ac4..2177c9c37f47 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -1421,6 +1421,15 @@
[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.
+
+
+
+
+
+
+ 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.
+
+
@@ -1428,6 +1437,16 @@
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].
+
+
+
+
+
+
+
+ Changes type and polygon of the client side decoration (UI element used to move or resize window) specified by [param id].
+
+
@@ -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].
+
+
+
+
+ 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].
+
+
@@ -1579,6 +1605,14 @@
Moves the window specified by [param window_id] to the foreground, so that it is visible over other windows.
+
+
+
+
+
+ Removes client side decoration (UI element used to move or resize window) specified by [param id].
+
+
@@ -1895,6 +1929,9 @@
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]
+
+ Display server supports client side decorations. See [method window_add_decoration], [method window_change_decoration], and [method window_remove_decoration].
+
Makes the mouse cursor visible if it is hidden.
@@ -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.
+
+ Window top-left resize element.
+
+
+ Window top resize element.
+
+
+ Window top-right resize element.
+
+
+ Window left resize element.
+
+
+ Window right resize element.
+
+
+ Window bottom-left resize element.
+
+
+ Window bottom resize element.
+
+
+ Window bottom-right resize element.
+
+
+ Window move element.
+
+
+ Element excluded from window decorations.
+
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]).
diff --git a/doc/classes/WindowDecoration.xml b/doc/classes/WindowDecoration.xml
new file mode 100644
index 000000000000..ab74e61fe99d
--- /dev/null
+++ b/doc/classes/WindowDecoration.xml
@@ -0,0 +1,24 @@
+
+
+
+ A move / resize region for the native [Window].
+
+
+ A move / resize region, used to implement client side decorations for the native [Window].
+
+
+
+
+
+ Move / resize region type.
+
+
+ If [code]true[/code], polygonal region is used instead of control bounds.
+
+
+ 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.
+
+
+
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index f056a477c41b..3654541e925b 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -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));
}
}
@@ -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)));
@@ -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);
@@ -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);
@@ -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();
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 4a283983c8d2..d06c3ab8f597 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -46,6 +46,7 @@ class ColorPicker;
class ConfirmationDialog;
class Control;
class FileDialog;
+class HBoxContainer;
class MenuBar;
class MenuButton;
class OptionButton;
@@ -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;
diff --git a/editor/gui/editor_title_bar.cpp b/editor/gui/editor_title_bar.cpp
index c251c70c6de9..6abe7c6d485b 100644
--- a/editor/gui/editor_title_bar.cpp
+++ b/editor/gui/editor_title_bar.cpp
@@ -30,55 +30,129 @@
#include "editor_title_bar.h"
-void EditorTitleBar::gui_input(const Ref &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 rects;
+ int prev_pos = 0;
+ int count = get_child_count();
+ for (int i = 0; i < count; i++) {
+ Control *n = Object::cast_to(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 mm = p_event;
- if (mm.is_valid() && moving) {
- if (mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
- Window *w = Object::cast_to(get_viewport());
- if (w) {
- Point2 mouse = DisplayServer::get_singleton()->mouse_get_position();
- w->set_position(mouse - click_pos);
+ for (Rect2i &rect : rects) {
+ Vector 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(p_child);
+ if (!control) {
+ return;
+ }
- Ref mb = p_event;
- if (mb.is_valid() && has_point(mb->get_position())) {
- Window *w = Object::cast_to(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(p_child)) {
+ return;
+ }
+
+ _update_rects();
+}
+
+void EditorTitleBar::remove_child_notify(Node *p_child) {
+ Control::remove_child_notify(p_child);
+
+ Control *control = Object::cast_to(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 {
diff --git a/editor/gui/editor_title_bar.h b/editor/gui/editor_title_bar.h
index 13fd5d6cdbef..a5048156683d 100644
--- a/editor/gui/editor_title_bar.h
+++ b/editor/gui/editor_title_bar.h
@@ -37,14 +37,21 @@
class EditorTitleBar : public HBoxContainer {
GDCLASS(EditorTitleBar, HBoxContainer);
- Point2i click_pos;
- bool moving = false;
+ void _update_rects();
+
+ Vector ids;
bool can_move = false;
protected:
- virtual void gui_input(const Ref &p_event) override;
+ void _notification(int p_notification);
static void _bind_methods() {}
+ virtual void _global_transform_changed() override;
+
+ virtual void add_child_notify(Node *p_child) override;
+ virtual void move_child_notify(Node *p_child) override;
+ virtual void remove_child_notify(Node *p_child) override;
+
public:
void set_can_move_window(bool p_enabled);
bool get_can_move_window() const;
diff --git a/editor/icons/WindowDecoration.svg b/editor/icons/WindowDecoration.svg
new file mode 100644
index 000000000000..93acc3317175
--- /dev/null
+++ b/editor/icons/WindowDecoration.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp
index 990b8df49d93..8fb839e161d3 100644
--- a/editor/plugins/abstract_polygon_2d_editor.cpp
+++ b/editor/plugins/abstract_polygon_2d_editor.cpp
@@ -93,7 +93,7 @@ void AbstractPolygon2DEditor::_set_polygon(int p_idx, const Variant &p_polygon)
}
void AbstractPolygon2DEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) {
- Node2D *node = _get_node();
+ CanvasItem *node = _get_node();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->add_do_method(node, "set_polygon", p_polygon);
undo_redo->add_undo_method(node, "set_polygon", p_previous);
diff --git a/editor/plugins/abstract_polygon_2d_editor.h b/editor/plugins/abstract_polygon_2d_editor.h
index 66d4e1b7ef04..e1eb2b9c92ac 100644
--- a/editor/plugins/abstract_polygon_2d_editor.h
+++ b/editor/plugins/abstract_polygon_2d_editor.h
@@ -117,7 +117,7 @@ class AbstractPolygon2DEditor : public HBoxContainer {
bool _is_empty() const;
- virtual Node2D *_get_node() const = 0;
+ virtual CanvasItem *_get_node() const = 0;
virtual void _set_node(Node *p_polygon) = 0;
virtual bool _is_line() const;
diff --git a/editor/plugins/collision_polygon_2d_editor_plugin.cpp b/editor/plugins/collision_polygon_2d_editor_plugin.cpp
index 7f4e0d3f2771..c7e72c816b0b 100644
--- a/editor/plugins/collision_polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/collision_polygon_2d_editor_plugin.cpp
@@ -30,7 +30,7 @@
#include "collision_polygon_2d_editor_plugin.h"
-Node2D *CollisionPolygon2DEditor::_get_node() const {
+CanvasItem *CollisionPolygon2DEditor::_get_node() const {
return node;
}
diff --git a/editor/plugins/collision_polygon_2d_editor_plugin.h b/editor/plugins/collision_polygon_2d_editor_plugin.h
index 61e6cc3ddaa3..1f2681e2af82 100644
--- a/editor/plugins/collision_polygon_2d_editor_plugin.h
+++ b/editor/plugins/collision_polygon_2d_editor_plugin.h
@@ -40,7 +40,7 @@ class CollisionPolygon2DEditor : public AbstractPolygon2DEditor {
CollisionPolygon2D *node = nullptr;
protected:
- virtual Node2D *_get_node() const override;
+ virtual CanvasItem *_get_node() const override;
virtual void _set_node(Node *p_polygon) override;
public:
diff --git a/editor/plugins/light_occluder_2d_editor_plugin.cpp b/editor/plugins/light_occluder_2d_editor_plugin.cpp
index e3b59f9bfbcd..44ed6e4d7c02 100644
--- a/editor/plugins/light_occluder_2d_editor_plugin.cpp
+++ b/editor/plugins/light_occluder_2d_editor_plugin.cpp
@@ -42,7 +42,7 @@ Ref LightOccluder2DEditor::_ensure_occluder() const {
return occluder;
}
-Node2D *LightOccluder2DEditor::_get_node() const {
+CanvasItem *LightOccluder2DEditor::_get_node() const {
return node;
}
diff --git a/editor/plugins/light_occluder_2d_editor_plugin.h b/editor/plugins/light_occluder_2d_editor_plugin.h
index 01c8a185b6a8..dc999a4c0e85 100644
--- a/editor/plugins/light_occluder_2d_editor_plugin.h
+++ b/editor/plugins/light_occluder_2d_editor_plugin.h
@@ -42,7 +42,7 @@ class LightOccluder2DEditor : public AbstractPolygon2DEditor {
Ref _ensure_occluder() const;
protected:
- virtual Node2D *_get_node() const override;
+ virtual CanvasItem *_get_node() const override;
virtual void _set_node(Node *p_polygon) override;
virtual bool _is_line() const override;
diff --git a/editor/plugins/line_2d_editor_plugin.cpp b/editor/plugins/line_2d_editor_plugin.cpp
index 0185617c368c..0cbcec5d1da2 100644
--- a/editor/plugins/line_2d_editor_plugin.cpp
+++ b/editor/plugins/line_2d_editor_plugin.cpp
@@ -33,7 +33,7 @@
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
-Node2D *Line2DEditor::_get_node() const {
+CanvasItem *Line2DEditor::_get_node() const {
return node;
}
@@ -54,7 +54,7 @@ void Line2DEditor::_set_polygon(int p_idx, const Variant &p_polygon) const {
}
void Line2DEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) {
- Node2D *_node = _get_node();
+ CanvasItem *_node = _get_node();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->add_do_method(_node, "set_points", p_polygon);
undo_redo->add_undo_method(_node, "set_points", p_previous);
diff --git a/editor/plugins/line_2d_editor_plugin.h b/editor/plugins/line_2d_editor_plugin.h
index f91fec80dd4e..664fea636c17 100644
--- a/editor/plugins/line_2d_editor_plugin.h
+++ b/editor/plugins/line_2d_editor_plugin.h
@@ -40,7 +40,7 @@ class Line2DEditor : public AbstractPolygon2DEditor {
Line2D *node = nullptr;
protected:
- virtual Node2D *_get_node() const override;
+ virtual CanvasItem *_get_node() const override;
virtual void _set_node(Node *p_line) override;
virtual bool _is_line() const override;
diff --git a/editor/plugins/navigation_obstacle_2d_editor_plugin.cpp b/editor/plugins/navigation_obstacle_2d_editor_plugin.cpp
index a0ef3f9a2059..8da952ca3990 100644
--- a/editor/plugins/navigation_obstacle_2d_editor_plugin.cpp
+++ b/editor/plugins/navigation_obstacle_2d_editor_plugin.cpp
@@ -33,7 +33,7 @@
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
-Node2D *NavigationObstacle2DEditor::_get_node() const {
+CanvasItem *NavigationObstacle2DEditor::_get_node() const {
return node;
}
diff --git a/editor/plugins/navigation_obstacle_2d_editor_plugin.h b/editor/plugins/navigation_obstacle_2d_editor_plugin.h
index ae5b3a056aee..164cb5af6eab 100644
--- a/editor/plugins/navigation_obstacle_2d_editor_plugin.h
+++ b/editor/plugins/navigation_obstacle_2d_editor_plugin.h
@@ -40,7 +40,7 @@ class NavigationObstacle2DEditor : public AbstractPolygon2DEditor {
NavigationObstacle2D *node = nullptr;
protected:
- virtual Node2D *_get_node() const override;
+ virtual CanvasItem *_get_node() const override;
virtual void _set_node(Node *p_polygon) override;
virtual Variant _get_polygon(int p_idx) const override;
diff --git a/editor/plugins/navigation_polygon_editor_plugin.cpp b/editor/plugins/navigation_polygon_editor_plugin.cpp
index c11a7cf20e2f..236fff47b736 100644
--- a/editor/plugins/navigation_polygon_editor_plugin.cpp
+++ b/editor/plugins/navigation_polygon_editor_plugin.cpp
@@ -45,7 +45,7 @@ Ref NavigationPolygonEditor::_ensure_navpoly() const {
return navpoly;
}
-Node2D *NavigationPolygonEditor::_get_node() const {
+CanvasItem *NavigationPolygonEditor::_get_node() const {
return node;
}
diff --git a/editor/plugins/navigation_polygon_editor_plugin.h b/editor/plugins/navigation_polygon_editor_plugin.h
index 4d6d245cc506..80d4f3678d93 100644
--- a/editor/plugins/navigation_polygon_editor_plugin.h
+++ b/editor/plugins/navigation_polygon_editor_plugin.h
@@ -68,7 +68,7 @@ class NavigationPolygonEditor : public AbstractPolygon2DEditor {
protected:
void _notification(int p_what);
- virtual Node2D *_get_node() const override;
+ virtual CanvasItem *_get_node() const override;
virtual void _set_node(Node *p_polygon) override;
virtual int _get_polygon_count() const override;
diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp
index 8ab08ff28f22..675e8894922c 100644
--- a/editor/plugins/polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/polygon_2d_editor_plugin.cpp
@@ -77,7 +77,7 @@ class UVEditDialog : public AcceptDialog {
}
};
-Node2D *Polygon2DEditor::_get_node() const {
+CanvasItem *Polygon2DEditor::_get_node() const {
return node;
}
diff --git a/editor/plugins/polygon_2d_editor_plugin.h b/editor/plugins/polygon_2d_editor_plugin.h
index 4e1cd7172e11..dcd5f547597d 100644
--- a/editor/plugins/polygon_2d_editor_plugin.h
+++ b/editor/plugins/polygon_2d_editor_plugin.h
@@ -165,7 +165,7 @@ class Polygon2DEditor : public AbstractPolygon2DEditor {
int _get_polygon_count() const override;
protected:
- virtual Node2D *_get_node() const override;
+ virtual CanvasItem *_get_node() const override;
virtual void _set_node(Node *p_polygon) override;
virtual Vector2 _get_offset(int p_idx) const override;
diff --git a/editor/plugins/window_decoration_editor_plugin.cpp b/editor/plugins/window_decoration_editor_plugin.cpp
new file mode 100644
index 000000000000..77402a144d7c
--- /dev/null
+++ b/editor/plugins/window_decoration_editor_plugin.cpp
@@ -0,0 +1,50 @@
+/**************************************************************************/
+/* window_decoration_editor_plugin.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "window_decoration_editor_plugin.h"
+
+CanvasItem *WindowDecorationEditor::_get_node() const {
+ return node;
+}
+
+void WindowDecorationEditor::_set_node(Node *p_polygon) {
+ WindowDecoration *wd = Object::cast_to(p_polygon);
+ if (wd && wd->is_non_rectangular_region()) {
+ node = wd;
+ } else {
+ node = nullptr;
+ }
+}
+
+WindowDecorationEditor::WindowDecorationEditor() {}
+
+WindowDecorationEditorPlugin::WindowDecorationEditorPlugin() :
+ AbstractPolygon2DEditorPlugin(memnew(WindowDecorationEditor), "WindowDecoration") {
+}
diff --git a/editor/plugins/window_decoration_editor_plugin.h b/editor/plugins/window_decoration_editor_plugin.h
new file mode 100644
index 000000000000..d416ebaf17aa
--- /dev/null
+++ b/editor/plugins/window_decoration_editor_plugin.h
@@ -0,0 +1,57 @@
+/**************************************************************************/
+/* window_decoration_editor_plugin.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef WINDOW_DECORATION_EDITOR_PLUGIN_H
+#define WINDOW_DECORATION_EDITOR_PLUGIN_H
+
+#include "editor/plugins/abstract_polygon_2d_editor.h"
+#include "scene/gui/window_decoration.h"
+
+class WindowDecorationEditor : public AbstractPolygon2DEditor {
+ GDCLASS(WindowDecorationEditor, AbstractPolygon2DEditor);
+
+ WindowDecoration *node = nullptr;
+
+protected:
+ virtual CanvasItem *_get_node() const override;
+ virtual void _set_node(Node *p_polygon) override;
+
+public:
+ WindowDecorationEditor();
+};
+
+class WindowDecorationEditorPlugin : public AbstractPolygon2DEditorPlugin {
+ GDCLASS(WindowDecorationEditorPlugin, AbstractPolygon2DEditorPlugin);
+
+public:
+ WindowDecorationEditorPlugin();
+};
+
+#endif // WINDOW_DECORATION_EDITOR_PLUGIN_H
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index eb5e8d2a727c..cef2dbfe2029 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -1049,17 +1049,23 @@ void ProjectManager::_files_dropped(PackedStringArray p_files) {
void ProjectManager::_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 = left_hbox->get_minimum_size().x;
+ int right_sp = right_hbox->get_minimum_size().x;
if (left_menu_spacer) {
int w = (root_container->is_layout_rtl()) ? margin.y : margin.x;
left_menu_spacer->set_custom_minimum_size(Size2(w, 0));
- right_spacer->set_custom_minimum_size(Size2(w, 0));
+ left_sp += w;
}
if (right_menu_spacer) {
int w = (root_container->is_layout_rtl()) ? margin.x : margin.y;
right_menu_spacer->set_custom_minimum_size(Size2(w, 0));
- left_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));
}
}
@@ -1182,10 +1188,10 @@ ProjectManager::ProjectManager() {
title_bar->add_child(left_menu_spacer);
}
- HBoxContainer *left_hbox = memnew(HBoxContainer);
+ left_hbox = memnew(HBoxContainer);
left_hbox->set_alignment(BoxContainer::ALIGNMENT_BEGIN);
- left_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);
left_hbox->set_stretch_ratio(1.0);
+ left_hbox->set_mouse_filter(Control::MOUSE_FILTER_STOP);
title_bar->add_child(left_hbox);
title_bar_logo = memnew(Button);
@@ -1193,32 +1199,38 @@ ProjectManager::ProjectManager() {
left_hbox->add_child(title_bar_logo);
title_bar_logo->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_show_about));
- if (can_expand) {
- // Spacer to center main toggles.
- left_spacer = memnew(Control);
- left_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
- title_bar->add_child(left_spacer);
- }
+ // Spacer to center main toggles.
+ left_spacer = memnew(Control);
+ left_spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ left_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ 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);
main_view_toggles = memnew(HBoxContainer);
main_view_toggles->set_alignment(BoxContainer::ALIGNMENT_CENTER);
- main_view_toggles->set_h_size_flags(Control::SIZE_EXPAND_FILL);
main_view_toggles->set_stretch_ratio(2.0);
+ main_view_toggles->set_mouse_filter(Control::MOUSE_FILTER_STOP);
title_bar->add_child(main_view_toggles);
- if (can_expand) {
- // Spacer to center main toggles.
- right_spacer = memnew(Control);
- right_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
- title_bar->add_child(right_spacer);
- }
+ // Spacer to center main toggles.
+ right_spacer = memnew(Control);
+ right_spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ right_spacer->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ 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);
main_view_toggles_group.instantiate();
- HBoxContainer *right_hbox = memnew(HBoxContainer);
+ right_hbox = memnew(HBoxContainer);
right_hbox->set_alignment(BoxContainer::ALIGNMENT_END);
- right_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);
right_hbox->set_stretch_ratio(1.0);
+ right_hbox->set_mouse_filter(Control::MOUSE_FILTER_STOP);
title_bar->add_child(right_hbox);
quick_settings_button = memnew(Button);
diff --git a/editor/project_manager.h b/editor/project_manager.h
index 07da0059c01a..a8ff5c72c73d 100644
--- a/editor/project_manager.h
+++ b/editor/project_manager.h
@@ -80,8 +80,12 @@ class ProjectManager : public Control {
EditorTitleBar *title_bar = nullptr;
Control *left_menu_spacer = nullptr;
Control *left_spacer = nullptr;
+ Control *left_spacer_al = nullptr;
+ HBoxContainer *left_hbox = nullptr;
Control *right_menu_spacer = nullptr;
Control *right_spacer = nullptr;
+ Control *right_spacer_al = nullptr;
+ HBoxContainer *right_hbox = nullptr;
Button *title_bar_logo = nullptr;
HBoxContainer *main_view_toggles = nullptr;
Button *quick_settings_button = nullptr;
diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp
index 2d3cbfb1e3c5..068849417aab 100644
--- a/editor/register_editor_types.cpp
+++ b/editor/register_editor_types.cpp
@@ -129,6 +129,7 @@
#include "editor/plugins/version_control_editor_plugin.h"
#include "editor/plugins/visual_shader_editor_plugin.h"
#include "editor/plugins/voxel_gi_editor_plugin.h"
+#include "editor/plugins/window_decoration_editor_plugin.h"
#include "editor/register_exporters.h"
void register_editor_types() {
@@ -269,6 +270,9 @@ void register_editor_types() {
EditorPlugins::add_by_type();
EditorPlugins::add_by_type();
+ // GUI
+ EditorPlugins::add_by_type();
+
// For correct doc generation.
GLOBAL_DEF("editor/run/main_run_args", "");
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index a9c94bd8238e..28a62d7bdc1b 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -36,6 +36,7 @@
#include "x11/key_mapping_x11.h"
#include "core/config/project_settings.h"
+#include "core/math/geometry_2d.h"
#include "core/math/math_funcs.h"
#include "core/string/print_string.h"
#include "core/string/ustring.h"
@@ -69,6 +70,16 @@
#define _NET_WM_STATE_REMOVE 0L // remove/unset property
#define _NET_WM_STATE_ADD 1L // add/set property
+#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0L
+#define _NET_WM_MOVERESIZE_SIZE_TOP 1L
+#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2L
+#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3L
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4L
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5L
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6L
+#define _NET_WM_MOVERESIZE_SIZE_LEFT 7L
+#define _NET_WM_MOVERESIZE_MOVE 8L
+
// 2.2 is the first release with multitouch
#define XINPUT_CLIENT_VERSION_MAJOR 2
#define XINPUT_CLIENT_VERSION_MINOR 2
@@ -140,6 +151,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const {
#endif
case FEATURE_CLIPBOARD_PRIMARY:
case FEATURE_TEXT_TO_SPEECH:
+ case FEATURE_CLIENT_SIDE_DECORATIONS:
return true;
case FEATURE_SCREEN_CAPTURE:
return !xwayland;
@@ -1945,6 +1957,92 @@ void DisplayServerX11::window_set_mouse_passthrough(const Vector &p_reg
_update_window_mouse_passthrough(p_window);
}
+int DisplayServerX11::window_add_decoration(const Vector &p_region, DisplayServer::WindowDecorationType p_dec_type, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX_V(p_dec_type, DisplayServer::WINDOW_DECORATION_MAX, -1);
+ ERR_FAIL_COND_V(!windows.has(p_window), -1);
+ WindowData &wd = windows[p_window];
+
+ int did = wd.decor_id++;
+ if (p_dec_type == WINDOW_DECORATION_PASS) {
+ wd.decor_pass[did].region = p_region;
+ wd.decor_pass[did].dec_type = p_dec_type;
+ } else {
+ wd.decor[did].region = p_region;
+ wd.decor[did].dec_type = p_dec_type;
+ }
+
+ return did;
+}
+
+Array DisplayServerX11::window_get_decorations(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Array());
+ const WindowData &wd = windows[p_window];
+
+ Array ret;
+ for (const KeyValue &E : wd.decor) {
+ Dictionary decor;
+ decor["id"] = E.key;
+ decor["region"] = E.value.region;
+ decor["type"] = E.value.dec_type;
+ ret.push_back(decor);
+ }
+ for (const KeyValue &E : wd.decor_pass) {
+ Dictionary decor;
+ decor["id"] = E.key;
+ decor["region"] = E.value.region;
+ decor["type"] = E.value.dec_type;
+ ret.push_back(decor);
+ }
+ return ret;
+}
+
+void DisplayServerX11::window_change_decoration(int p_rect_id, const Vector &p_region, DisplayServer::WindowDecorationType p_dec_type, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX(p_dec_type, DisplayServer::WINDOW_DECORATION_MAX);
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if (wd.decor.has(p_rect_id)) {
+ if (p_dec_type == WINDOW_DECORATION_PASS) {
+ wd.decor.erase(p_rect_id);
+ wd.decor_pass[p_rect_id].region = p_region;
+ wd.decor_pass[p_rect_id].dec_type = p_dec_type;
+ } else {
+ wd.decor[p_rect_id].region = p_region;
+ wd.decor[p_rect_id].dec_type = p_dec_type;
+ }
+ }
+ if (wd.decor_pass.has(p_rect_id)) {
+ if (p_dec_type != WINDOW_DECORATION_PASS) {
+ wd.decor_pass.erase(p_rect_id);
+ wd.decor[p_rect_id].region = p_region;
+ wd.decor[p_rect_id].dec_type = p_dec_type;
+ } else {
+ wd.decor_pass[p_rect_id].region = p_region;
+ wd.decor_pass[p_rect_id].dec_type = p_dec_type;
+ }
+ }
+}
+
+void DisplayServerX11::window_remove_decoration(int p_rect_id, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if (wd.decor.has(p_rect_id)) {
+ wd.decor.erase(p_rect_id);
+ }
+ if (wd.decor_pass.has(p_rect_id)) {
+ wd.decor_pass.erase(p_rect_id);
+ }
+}
+
void DisplayServerX11::_update_window_mouse_passthrough(WindowID p_window) {
ERR_FAIL_COND(!windows.has(p_window));
ERR_FAIL_COND(!xshaped_ext_ok);
@@ -4337,6 +4435,27 @@ bool DisplayServerX11::_window_focus_check() {
return has_focus;
}
+void DisplayServerX11::_process_window_drag(WindowID p_window, XEvent p_event, int p_dec_type) {
+ XClientMessageEvent m;
+ memset(&m, 0, sizeof(m));
+
+ XUngrabPointer(x11_display, CurrentTime);
+ XFlush(x11_display);
+
+ m.type = ClientMessage;
+ m.window = windows[p_window].x11_window;
+ m.message_type = XInternAtom(x11_display, "_NET_WM_MOVERESIZE", True);
+ m.format = 32;
+ m.data.l[0] = p_event.xbutton.x_root;
+ m.data.l[1] = p_event.xbutton.y_root;
+ m.data.l[2] = p_dec_type;
+ m.data.l[3] = Button1;
+
+ XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&m);
+
+ XSync(x11_display, 0);
+}
+
void DisplayServerX11::process_events() {
ERR_FAIL_COND(!Thread::is_main_thread());
@@ -4781,7 +4900,70 @@ void DisplayServerX11::process_events() {
_window_changed(&event);
} break;
- case ButtonPress:
+ case ButtonPress: {
+ if (event.xbutton.button == 1) {
+ bool drag_event = false;
+ bool pass = false;
+ for (const KeyValue &E : windows[window_id].decor_pass) {
+ if (Geometry2D::is_point_in_polygon(Vector2i(event.xbutton.x, event.xbutton.y), E.value.region)) {
+ pass = true;
+ break;
+ }
+ }
+
+ if (!pass) {
+ for (const KeyValue &E : windows[window_id].decor) {
+ if (Geometry2D::is_point_in_polygon(Vector2i(event.xbutton.x, event.xbutton.y), E.value.region)) {
+ switch (E.value.dec_type) {
+ case DisplayServer::WINDOW_DECORATION_TOP_LEFT: {
+ _process_window_drag(window_id, event, _NET_WM_MOVERESIZE_SIZE_TOPLEFT);
+ drag_event = true;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_TOP: {
+ _process_window_drag(window_id, event, _NET_WM_MOVERESIZE_SIZE_TOP);
+ drag_event = true;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_TOP_RIGHT: {
+ _process_window_drag(window_id, event, _NET_WM_MOVERESIZE_SIZE_TOPRIGHT);
+ drag_event = true;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_LEFT: {
+ _process_window_drag(window_id, event, _NET_WM_MOVERESIZE_SIZE_LEFT);
+ drag_event = true;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_RIGHT: {
+ _process_window_drag(window_id, event, _NET_WM_MOVERESIZE_SIZE_RIGHT);
+ drag_event = true;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_BOTTOM_LEFT: {
+ _process_window_drag(window_id, event, _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT);
+ drag_event = true;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_BOTTOM: {
+ _process_window_drag(window_id, event, _NET_WM_MOVERESIZE_SIZE_BOTTOM);
+ drag_event = true;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_BOTTOM_RIGHT: {
+ _process_window_drag(window_id, event, _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT);
+ drag_event = true;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_MOVE: {
+ _process_window_drag(window_id, event, _NET_WM_MOVERESIZE_MOVE);
+ drag_event = true;
+ } break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ if (drag_event) {
+ break;
+ }
+ }
+ [[fallthrough]];
+ }
case ButtonRelease: {
if (ime_window_event || ignore_events) {
break;
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index 0cbfbe51ef1f..4905c62d14a1 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -162,7 +162,16 @@ class DisplayServerX11 : public DisplayServer {
FreeDesktopPortalDesktop *portal_desktop = nullptr;
#endif
+ struct DecorData {
+ Vector region;
+ WindowDecorationType dec_type = WINDOW_DECORATION_MOVE;
+ };
+
struct WindowData {
+ int decor_id = 0;
+ HashMap decor;
+ HashMap decor_pass;
+
Window x11_window;
Window x11_xim_window;
Window parent;
@@ -361,6 +370,8 @@ class DisplayServerX11 : public DisplayServer {
void _dispatch_input_event(const Ref &p_event);
void _set_input_focus(Window p_window, int p_revert_to);
+ void _process_window_drag(WindowID p_window, XEvent p_event, int p_dec_type);
+
mutable Mutex events_mutex;
Thread events_thread;
SafeFlag events_thread_done;
@@ -456,6 +467,11 @@ class DisplayServerX11 : public DisplayServer {
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_mouse_passthrough(const Vector &p_region, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual int window_add_decoration(const Vector &p_region, WindowDecorationType p_dec_type, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_change_decoration(int p_rect_id, const Vector &p_region, WindowDecorationType p_dec_type, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_remove_decoration(int p_rect_id, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Array window_get_decorations(WindowID p_window = MAIN_WINDOW_ID) const override;
+
virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index 97af6d0a5a25..4b78017d0166 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -83,7 +83,16 @@ class DisplayServerMacOS : public DisplayServer {
KeyLocation location = KeyLocation::UNSPECIFIED;
};
+ struct DecorData {
+ Vector region;
+ WindowDecorationType dec_type = WINDOW_DECORATION_MOVE;
+ };
+
struct WindowData {
+ int decor_id = 0;
+ HashMap decor;
+ HashMap decor_pass;
+
id window_delegate;
id window_object;
id window_view;
@@ -347,6 +356,11 @@ class DisplayServerMacOS : public DisplayServer {
virtual Size2i window_get_title_size(const String &p_title, WindowID p_window) const override;
virtual void window_set_mouse_passthrough(const Vector &p_region, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual int window_add_decoration(const Vector &p_region, WindowDecorationType p_dec_type, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_change_decoration(int p_rect_id, const Vector &p_region, WindowDecorationType p_dec_type, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_remove_decoration(int p_rect_id, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Array window_get_decorations(WindowID p_window = MAIN_WINDOW_ID) const override;
+
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index f1078d9868fd..2f6b1d805462 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -765,6 +765,7 @@
case FEATURE_SCREEN_CAPTURE:
case FEATURE_STATUS_INDICATOR:
case FEATURE_NATIVE_HELP:
+ case FEATURE_CLIENT_SIDE_DECORATIONS:
return true;
default: {
}
@@ -1868,6 +1869,92 @@
wd.mpath = p_region;
}
+int DisplayServerMacOS::window_add_decoration(const Vector &p_region, DisplayServer::WindowDecorationType p_dec_type, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX_V(p_dec_type, DisplayServer::WINDOW_DECORATION_MAX, -1);
+ ERR_FAIL_COND_V(!windows.has(p_window), -1);
+ WindowData &wd = windows[p_window];
+
+ int did = wd.decor_id++;
+ if (p_dec_type == WINDOW_DECORATION_PASS) {
+ wd.decor_pass[did].region = p_region;
+ wd.decor_pass[did].dec_type = p_dec_type;
+ } else {
+ wd.decor[did].region = p_region;
+ wd.decor[did].dec_type = p_dec_type;
+ }
+
+ return did;
+}
+
+Array DisplayServerMacOS::window_get_decorations(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Array());
+ const WindowData &wd = windows[p_window];
+
+ Array ret;
+ for (const KeyValue &E : wd.decor) {
+ Dictionary decor;
+ decor["id"] = E.key;
+ decor["region"] = E.value.region;
+ decor["type"] = E.value.dec_type;
+ ret.push_back(decor);
+ }
+ for (const KeyValue &E : wd.decor_pass) {
+ Dictionary decor;
+ decor["id"] = E.key;
+ decor["region"] = E.value.region;
+ decor["type"] = E.value.dec_type;
+ ret.push_back(decor);
+ }
+ return ret;
+}
+
+void DisplayServerMacOS::window_change_decoration(int p_rect_id, const Vector &p_region, DisplayServer::WindowDecorationType p_dec_type, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX(p_dec_type, DisplayServer::WINDOW_DECORATION_MAX);
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if (wd.decor.has(p_rect_id)) {
+ if (p_dec_type == WINDOW_DECORATION_PASS) {
+ wd.decor.erase(p_rect_id);
+ wd.decor_pass[p_rect_id].region = p_region;
+ wd.decor_pass[p_rect_id].dec_type = p_dec_type;
+ } else {
+ wd.decor[p_rect_id].region = p_region;
+ wd.decor[p_rect_id].dec_type = p_dec_type;
+ }
+ }
+ if (wd.decor_pass.has(p_rect_id)) {
+ if (p_dec_type != WINDOW_DECORATION_PASS) {
+ wd.decor_pass.erase(p_rect_id);
+ wd.decor[p_rect_id].region = p_region;
+ wd.decor[p_rect_id].dec_type = p_dec_type;
+ } else {
+ wd.decor_pass[p_rect_id].region = p_region;
+ wd.decor_pass[p_rect_id].dec_type = p_dec_type;
+ }
+ }
+}
+
+void DisplayServerMacOS::window_remove_decoration(int p_rect_id, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if (wd.decor.has(p_rect_id)) {
+ wd.decor.erase(p_rect_id);
+ }
+ if (wd.decor_pass.has(p_rect_id)) {
+ wd.decor_pass.erase(p_rect_id);
+ }
+}
+
int DisplayServerMacOS::window_get_current_screen(WindowID p_window) const {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), -1);
@@ -2257,6 +2344,9 @@
} break;
case WINDOW_MODE_MAXIMIZED: {
if (NSEqualRects([wd.window_object frame], [[wd.window_object screen] visibleFrame])) {
+ if (!([wd.window_object styleMask] & NSWindowStyleMaskTitled)) { // Window should be resizable to zoom.
+ [wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskResizable];
+ }
[wd.window_object zoom:nil];
}
} break;
@@ -2267,7 +2357,7 @@
// Do nothing.
} break;
case WINDOW_MODE_MINIMIZED: {
- [wd.window_object performMiniaturize:nil];
+ [wd.window_object miniaturize:nil];
} break;
case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
@@ -2290,6 +2380,9 @@
} break;
case WINDOW_MODE_MAXIMIZED: {
if (!NSEqualRects([wd.window_object frame], [[wd.window_object screen] visibleFrame])) {
+ if (!([wd.window_object styleMask] & NSWindowStyleMaskTitled)) { // Window should be resizable to zoom.
+ [wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskResizable];
+ }
[wd.window_object zoom:nil];
}
} break;
@@ -2309,6 +2402,7 @@
return WINDOW_MODE_FULLSCREEN;
}
}
+
if (NSEqualRects([wd.window_object frame], [[wd.window_object screen] visibleFrame])) {
return WINDOW_MODE_MAXIMIZED;
}
@@ -2457,7 +2551,7 @@
}
wd.borderless = p_enabled;
if (p_enabled) {
- [wd.window_object setStyleMask:NSWindowStyleMaskBorderless];
+ [wd.window_object setStyleMask:NSWindowStyleMaskBorderless | NSWindowStyleMaskMiniaturizable];
} else {
if (wd.layered_window) {
wd.layered_window = false;
@@ -2496,7 +2590,7 @@
return;
}
if (p_enabled) {
- [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; // Force borderless.
+ [wd.window_object setStyleMask:NSWindowStyleMaskBorderless | NSWindowStyleMaskMiniaturizable]; // Force borderless.
} else if (!wd.borderless) {
[wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.extend_to_title ? NSWindowStyleMaskFullSizeContentView : 0) | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)];
}
@@ -2543,7 +2637,7 @@
return [wd.window_object styleMask] & NSWindowStyleMaskFullSizeContentView;
} break;
case WINDOW_FLAG_BORDERLESS: {
- return [wd.window_object styleMask] == NSWindowStyleMaskBorderless;
+ return !([wd.window_object styleMask] & NSWindowStyleMaskTitled);
} break;
case WINDOW_FLAG_ALWAYS_ON_TOP: {
if (wd.fullscreen) {
diff --git a/platform/macos/godot_content_view.h b/platform/macos/godot_content_view.h
index dc8d11be541b..bfcf486be413 100644
--- a/platform/macos/godot_content_view.h
+++ b/platform/macos/godot_content_view.h
@@ -68,8 +68,11 @@
bool last_pen_inverted;
bool ime_suppress_next_keyup;
id layer_delegate;
+ DisplayServer::WindowDecorationType current_drag;
+ bool moving_window;
}
+- (void)processWindowDrag:(NSEvent *)event drag:(DisplayServer::WindowDecorationType)drag;
- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor;
- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy;
- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index pressed:(bool)pressed outofstream:(bool)outofstream;
diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm
index 7d43ac9fe618..30ae2c2faec5 100644
--- a/platform/macos/godot_content_view.mm
+++ b/platform/macos/godot_content_view.mm
@@ -33,6 +33,8 @@
#include "display_server_macos.h"
#include "key_mapping_macos.h"
+#include "core/math/geometry_2d.h"
+
#include "main/main.h"
@implementation GodotContentLayerDelegate
@@ -118,6 +120,8 @@ - (id)init {
mouse_down_control = false;
ignore_momentum_scroll = false;
last_pen_inverted = false;
+ current_drag = DisplayServerMacOS::WINDOW_DECORATION_MAX;
+ moving_window = false;
[self updateTrackingAreas];
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
@@ -394,6 +398,56 @@ - (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index pressed:(boo
}
- (void)mouseDown:(NSEvent *)event {
+ moving_window = false;
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (ds && ds->has_window(window_id)) {
+ DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
+ ds->update_mouse_pos(wd, [event locationInWindow]);
+ if (event.clickCount >= 2) {
+ bool pass = false;
+ for (const KeyValue &E : wd.decor_pass) {
+ if (Geometry2D::is_point_in_polygon(wd.mouse_pos, E.value.region)) {
+ pass = true;
+ break;
+ }
+ }
+ if (!pass) {
+ for (const KeyValue &E : wd.decor) {
+ if (Geometry2D::is_point_in_polygon(wd.mouse_pos, E.value.region)) {
+ if (E.value.dec_type == DisplayServer::WINDOW_DECORATION_MOVE) {
+ if (ds->window_maximize_on_title_dbl_click()) {
+ [wd.window_object zoom:nil];
+ return;
+ } else if (ds->window_minimize_on_title_dbl_click()) {
+ [wd.window_object miniaturize:nil];
+ return;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ bool pass = false;
+ for (const KeyValue &E : wd.decor_pass) {
+ if (Geometry2D::is_point_in_polygon(wd.mouse_pos, E.value.region)) {
+ pass = true;
+ break;
+ }
+ }
+ if (!pass) {
+ for (const KeyValue &E : wd.decor) {
+ if (Geometry2D::is_point_in_polygon(wd.mouse_pos, E.value.region)) {
+ if (E.value.dec_type == DisplayServer::WINDOW_DECORATION_MOVE) {
+ current_drag = E.value.dec_type;
+ [wd.window_object performWindowDragWithEvent:event];
+ moving_window = true;
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
if (([event modifierFlags] & NSEventModifierFlagControl)) {
mouse_down_control = true;
[self processMouseEvent:event index:MouseButton::RIGHT pressed:true outofstream:false];
@@ -403,11 +457,95 @@ - (void)mouseDown:(NSEvent *)event {
}
}
+- (void)processWindowDrag:(NSEvent *)event drag:(DisplayServer::WindowDecorationType)drag {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (ds && ds->has_window(window_id)) {
+ DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
+ Size2i max_size = wd.max_size / ds->screen_get_max_scale();
+ Size2i min_size = wd.min_size / ds->screen_get_max_scale();
+ NSRect frame = [wd.window_object frame];
+ switch (drag) {
+ case DisplayServer::WINDOW_DECORATION_TOP_LEFT: {
+ int clamped_dx = CLAMP(frame.size.width - event.deltaX, min_size.x, max_size.x) - frame.size.width;
+ int clamped_dy = CLAMP(frame.size.height - event.deltaY, min_size.y, max_size.y) - frame.size.height;
+ [wd.window_object setFrame:NSMakeRect(frame.origin.x - clamped_dx, frame.origin.y, frame.size.width + clamped_dx, frame.size.height + clamped_dy) display:YES];
+ } break;
+ case DisplayServer::WINDOW_DECORATION_TOP: {
+ int clamped_dy = CLAMP(frame.size.height - event.deltaY, min_size.y, max_size.y) - frame.size.height;
+ [wd.window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height + clamped_dy) display:YES];
+ } break;
+ case DisplayServer::WINDOW_DECORATION_TOP_RIGHT: {
+ int clamped_dx = CLAMP(frame.size.width + event.deltaX, min_size.x, max_size.x) - frame.size.width;
+ int clamped_dy = CLAMP(frame.size.height - event.deltaY, min_size.y, max_size.y) - frame.size.height;
+ [wd.window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, frame.size.width + clamped_dx, frame.size.height + clamped_dy) display:YES];
+ } break;
+ case DisplayServer::WINDOW_DECORATION_LEFT: {
+ int clamped_dx = CLAMP(frame.size.width - event.deltaX, min_size.x, max_size.x) - frame.size.width;
+ [wd.window_object setFrame:NSMakeRect(frame.origin.x - clamped_dx, frame.origin.y, frame.size.width + clamped_dx, frame.size.height) display:YES];
+ } break;
+ case DisplayServer::WINDOW_DECORATION_RIGHT: {
+ int clamped_dx = CLAMP(frame.size.width + event.deltaX, min_size.x, max_size.x) - frame.size.width;
+ [wd.window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y, frame.size.width + clamped_dx, frame.size.height) display:YES];
+ } break;
+ case DisplayServer::WINDOW_DECORATION_BOTTOM_LEFT: {
+ int clamped_dx = CLAMP(frame.size.width - event.deltaX, min_size.x, max_size.x) - frame.size.width;
+ int clamped_dy = CLAMP(frame.size.height + event.deltaY, min_size.y, max_size.y) - frame.size.height;
+ [wd.window_object setFrame:NSMakeRect(frame.origin.x - clamped_dx, frame.origin.y - clamped_dy, frame.size.width + clamped_dx, frame.size.height + clamped_dy) display:YES];
+ } break;
+ case DisplayServer::WINDOW_DECORATION_BOTTOM: {
+ int clamped_dy = CLAMP(frame.size.height + event.deltaY, min_size.y, max_size.y) - frame.size.height;
+ [wd.window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y - clamped_dy, frame.size.width, frame.size.height + clamped_dy) display:YES];
+ } break;
+ case DisplayServer::WINDOW_DECORATION_BOTTOM_RIGHT: {
+ int clamped_dx = CLAMP(frame.size.width + event.deltaX, min_size.x, max_size.x) - frame.size.width;
+ int clamped_dy = CLAMP(frame.size.height + event.deltaY, min_size.y, max_size.y) - frame.size.height;
+ [wd.window_object setFrame:NSMakeRect(frame.origin.x, frame.origin.y - clamped_dy, frame.size.width + clamped_dx, frame.size.height + clamped_dy) display:YES];
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
- (void)mouseDragged:(NSEvent *)event {
+ if (moving_window) {
+ return;
+ }
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (ds && ds->has_window(window_id)) {
+ if (current_drag != DisplayServer::WINDOW_DECORATION_MAX) {
+ [self processWindowDrag:event drag:current_drag];
+ } else {
+ DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
+ ds->update_mouse_pos(wd, [event locationInWindow]);
+
+ bool pass = false;
+ for (const KeyValue &E : wd.decor_pass) {
+ if (Geometry2D::is_point_in_polygon(wd.mouse_pos, E.value.region)) {
+ pass = true;
+ break;
+ }
+ }
+ if (!pass) {
+ for (const KeyValue &E : wd.decor) {
+ if (Geometry2D::is_point_in_polygon(wd.mouse_pos, E.value.region)) {
+ if (E.value.dec_type <= DisplayServer::WINDOW_DECORATION_MOVE) {
+ current_drag = E.value.dec_type;
+ [self processWindowDrag:event drag:E.value.dec_type];
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
[self mouseMoved:event];
}
- (void)mouseUp:(NSEvent *)event {
+ moving_window = false;
+ current_drag = DisplayServer::WINDOW_DECORATION_MAX;
if (mouse_down_control) {
[self processMouseEvent:event index:MouseButton::RIGHT pressed:false outofstream:false];
} else {
diff --git a/platform/macos/godot_window_delegate.mm b/platform/macos/godot_window_delegate.mm
index 7749debfd6d4..536523494ba0 100644
--- a/platform/macos/godot_window_delegate.mm
+++ b/platform/macos/godot_window_delegate.mm
@@ -261,6 +261,10 @@ - (void)windowDidResize:(NSNotification *)notification {
wd.size.width = content_rect.size.width * scale;
wd.size.height = content_rect.size.height * scale;
+ if (!([wd.window_object styleMask] & NSWindowStyleMaskTitled)) {
+ [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
+ }
+
CALayer *layer = [wd.window_view layer];
if (layer) {
layer.contentsScale = scale;
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index e300bd1c472a..a759603a64e4 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -36,6 +36,7 @@
#include "core/config/project_settings.h"
#include "core/io/marshalls.h"
+#include "core/math/geometry_2d.h"
#include "core/version.h"
#include "drivers/png/png_driver_common.h"
#include "main/main.h"
@@ -136,6 +137,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_SCREEN_CAPTURE:
case FEATURE_STATUS_INDICATOR:
+ case FEATURE_CLIENT_SIDE_DECORATIONS:
return true;
default:
return false;
@@ -1808,6 +1810,92 @@ void DisplayServerWindows::window_set_mouse_passthrough(const Vector &p
_update_window_mouse_passthrough(p_window);
}
+int DisplayServerWindows::window_add_decoration(const Vector &p_region, DisplayServer::WindowDecorationType p_dec_type, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX_V(p_dec_type, DisplayServer::WINDOW_DECORATION_MAX, -1);
+ ERR_FAIL_COND_V(!windows.has(p_window), -1);
+ WindowData &wd = windows[p_window];
+
+ int did = wd.decor_id++;
+ if (p_dec_type == WINDOW_DECORATION_PASS) {
+ wd.decor_pass[did].region = p_region;
+ wd.decor_pass[did].dec_type = p_dec_type;
+ } else {
+ wd.decor[did].region = p_region;
+ wd.decor[did].dec_type = p_dec_type;
+ }
+
+ return did;
+}
+
+Array DisplayServerWindows::window_get_decorations(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Array());
+ const WindowData &wd = windows[p_window];
+
+ Array ret;
+ for (const KeyValue &E : wd.decor) {
+ Dictionary decor;
+ decor["id"] = E.key;
+ decor["region"] = E.value.region;
+ decor["type"] = E.value.dec_type;
+ ret.push_back(decor);
+ }
+ for (const KeyValue &E : wd.decor_pass) {
+ Dictionary decor;
+ decor["id"] = E.key;
+ decor["region"] = E.value.region;
+ decor["type"] = E.value.dec_type;
+ ret.push_back(decor);
+ }
+ return ret;
+}
+
+void DisplayServerWindows::window_change_decoration(int p_rect_id, const Vector &p_region, DisplayServer::WindowDecorationType p_dec_type, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX(p_dec_type, DisplayServer::WINDOW_DECORATION_MAX);
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if (wd.decor.has(p_rect_id)) {
+ if (p_dec_type == WINDOW_DECORATION_PASS) {
+ wd.decor.erase(p_rect_id);
+ wd.decor_pass[p_rect_id].region = p_region;
+ wd.decor_pass[p_rect_id].dec_type = p_dec_type;
+ } else {
+ wd.decor[p_rect_id].region = p_region;
+ wd.decor[p_rect_id].dec_type = p_dec_type;
+ }
+ }
+ if (wd.decor_pass.has(p_rect_id)) {
+ if (p_dec_type != WINDOW_DECORATION_PASS) {
+ wd.decor_pass.erase(p_rect_id);
+ wd.decor[p_rect_id].region = p_region;
+ wd.decor[p_rect_id].dec_type = p_dec_type;
+ } else {
+ wd.decor_pass[p_rect_id].region = p_region;
+ wd.decor_pass[p_rect_id].dec_type = p_dec_type;
+ }
+ }
+}
+
+void DisplayServerWindows::window_remove_decoration(int p_rect_id, WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+
+ if (wd.decor.has(p_rect_id)) {
+ wd.decor.erase(p_rect_id);
+ }
+ if (wd.decor_pass.has(p_rect_id)) {
+ wd.decor_pass.erase(p_rect_id);
+ }
+}
+
void DisplayServerWindows::_update_window_mouse_passthrough(WindowID p_window) {
ERR_FAIL_COND(!windows.has(p_window));
@@ -2228,8 +2316,17 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
SystemParametersInfoA(SPI_SETMOUSETRAILS, restore_mouse_trails, nullptr, 0);
restore_mouse_trails = 0;
}
+ } else if (wd.borderless) {
+ int cs = window_get_current_screen(p_window);
+ Rect2i srect = screen_get_usable_rect(cs);
+ Rect2i wrect = Rect2i(window_get_position_with_decorations(p_window), window_get_size_with_decorations(p_window));
+ if (wrect == srect) {
+ if (wd.pre_max_valid) {
+ MoveWindow(wd.hWnd, wd.pre_max_rect.left, wd.pre_max_rect.top, wd.pre_max_rect.right - wd.pre_max_rect.left, wd.pre_max_rect.bottom - wd.pre_max_rect.top, TRUE);
+ wd.pre_max_valid = false;
+ }
+ }
}
-
if (p_mode == WINDOW_MODE_WINDOWED) {
ShowWindow(wd.hWnd, SW_NORMAL);
wd.maximized = false;
@@ -2237,7 +2334,15 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
}
if (p_mode == WINDOW_MODE_MAXIMIZED) {
- ShowWindow(wd.hWnd, SW_MAXIMIZE);
+ if (wd.borderless) {
+ int cs = window_get_current_screen(p_window);
+ Rect2i srect = screen_get_usable_rect(cs);
+ GetWindowRect(wd.hWnd, &wd.pre_max_rect);
+ wd.pre_max_valid = true;
+ MoveWindow(wd.hWnd, srect.position.x, srect.position.y, srect.size.width, srect.size.height, TRUE);
+ } else {
+ ShowWindow(wd.hWnd, SW_MAXIMIZE);
+ }
wd.maximized = true;
wd.minimized = false;
}
@@ -2303,6 +2408,15 @@ DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_windo
return WINDOW_MODE_MINIMIZED;
} else if (wd.maximized) {
return WINDOW_MODE_MAXIMIZED;
+ } else if (wd.borderless) {
+ int cs = window_get_current_screen(p_window);
+ Rect2i srect = screen_get_usable_rect(cs);
+ Rect2i wrect = Rect2i(window_get_position_with_decorations(p_window), window_get_size_with_decorations(p_window));
+ if (wrect == srect) {
+ return WINDOW_MODE_MAXIMIZED;
+ } else {
+ return WINDOW_MODE_WINDOWED;
+ }
} else {
return WINDOW_MODE_WINDOWED;
}
@@ -3692,6 +3806,14 @@ DisplayServer::VSyncMode DisplayServerWindows::window_get_vsync_mode(WindowID p_
return DisplayServer::VSYNC_ENABLED;
}
+bool DisplayServerWindows::window_maximize_on_title_dbl_click() const {
+ return true;
+}
+
+bool DisplayServerWindows::window_minimize_on_title_dbl_click() const {
+ return false;
+}
+
void DisplayServerWindows::set_context(Context p_context) {
}
@@ -4082,6 +4204,56 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
} break;
case WM_NCHITTEST: {
+ POINT coords;
+ GetCursorPos(&coords);
+ ScreenToClient(windows[window_id].hWnd, &coords);
+
+ bool pass = false;
+ for (const KeyValue &E : windows[window_id].decor_pass) {
+ if (Geometry2D::is_point_in_polygon(Vector2i(coords.x, coords.y), E.value.region)) {
+ pass = true;
+ break;
+ }
+ }
+
+ if (!pass) {
+ for (const KeyValue &E : windows[window_id].decor) {
+ if (Geometry2D::is_point_in_polygon(Vector2i(coords.x, coords.y), E.value.region)) {
+ switch (E.value.dec_type) {
+ case DisplayServer::WINDOW_DECORATION_TOP_LEFT: {
+ return HTTOPLEFT;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_TOP: {
+ return HTTOP;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_TOP_RIGHT: {
+ return HTTOPRIGHT;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_LEFT: {
+ return HTLEFT;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_RIGHT: {
+ return HTRIGHT;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_BOTTOM_LEFT: {
+ return HTBOTTOMLEFT;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_BOTTOM: {
+ return HTBOTTOM;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_BOTTOM_RIGHT: {
+ return HTBOTTOMRIGHT;
+ } break;
+ case DisplayServer::WINDOW_DECORATION_MOVE: {
+ return HTCAPTION;
+ } break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
if (windows[window_id].mpass) {
return HTTRANSPARENT;
}
@@ -4877,6 +5049,60 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
Input::get_singleton()->parse_input_event(mm);
} break;
+ case WM_NCLBUTTONDBLCLK: {
+ POINT coords;
+ GetCursorPos(&coords);
+ ScreenToClient(windows[window_id].hWnd, &coords);
+
+ WindowData &wd = windows[window_id];
+ bool pass = false;
+ for (const KeyValue &E : wd.decor_pass) {
+ if (Geometry2D::is_point_in_polygon(Vector2i(coords.x, coords.y), E.value.region)) {
+ pass = true;
+ break;
+ }
+ }
+ if (!pass) {
+ for (const KeyValue &E : wd.decor) {
+ if (Geometry2D::is_point_in_polygon(Vector2(coords.x, coords.y), E.value.region)) {
+ if (E.value.dec_type == DisplayServer::WINDOW_DECORATION_MOVE) {
+ if (window_maximize_on_title_dbl_click()) {
+ if (wd.borderless) {
+ int cs = window_get_current_screen(window_id);
+ Rect2i srect = screen_get_usable_rect(cs);
+ Rect2i wrect = Rect2i(window_get_position_with_decorations(window_id), window_get_size_with_decorations(window_id));
+ if (wrect == srect) {
+ if (wd.pre_max_valid) {
+ MoveWindow(wd.hWnd, wd.pre_max_rect.left, wd.pre_max_rect.top, wd.pre_max_rect.right - wd.pre_max_rect.left, wd.pre_max_rect.bottom - wd.pre_max_rect.top, TRUE);
+ wd.pre_max_valid = false;
+ }
+ wd.maximized = false;
+ wd.minimized = false;
+ } else {
+ GetWindowRect(wd.hWnd, &wd.pre_max_rect);
+ wd.pre_max_valid = true;
+ MoveWindow(wd.hWnd, srect.position.x, srect.position.y, srect.size.width, srect.size.height, TRUE);
+ wd.maximized = true;
+ wd.minimized = false;
+ }
+ } else {
+ if (wd.maximized) {
+ ShowWindow(wd.hWnd, SW_RESTORE);
+ wd.maximized = false;
+ wd.minimized = false;
+ } else {
+ ShowWindow(wd.hWnd, SW_MAXIMIZE);
+ wd.maximized = true;
+ wd.minimized = false;
+ }
+ }
+ return 0;
+ }
+ }
+ }
+ }
+ }
+ } break;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
if (Input::get_singleton()->is_emulating_mouse_from_touch()) {
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 32ed8823d304..52ab67824b94 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -454,12 +454,23 @@ class DisplayServerWindows : public DisplayServer {
TTS_Windows *tts = nullptr;
NativeMenuWindows *native_menu = nullptr;
+ struct DecorData {
+ Vector region;
+ WindowDecorationType dec_type = WINDOW_DECORATION_MOVE;
+ };
+
struct WindowData {
+ int decor_id = 0;
+ HashMap decor;
+ HashMap decor_pass;
+
HWND hWnd;
Vector mpath;
bool create_completed = false;
+ bool pre_max_valid = false;
+ RECT pre_max_rect;
bool pre_fs_valid = false;
RECT pre_fs_rect;
bool maximized = false;
@@ -741,6 +752,11 @@ class DisplayServerWindows : public DisplayServer {
virtual Size2i window_get_title_size(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_mouse_passthrough(const Vector &p_region, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual int window_add_decoration(const Vector &p_region, WindowDecorationType p_dec_type, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_change_decoration(int p_rect_id, const Vector &p_region, WindowDecorationType p_dec_type, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_remove_decoration(int p_rect_id, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Array window_get_decorations(WindowID p_window = MAIN_WINDOW_ID) const override;
+
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
@@ -788,6 +804,9 @@ class DisplayServerWindows : public DisplayServer {
virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override;
virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
+ virtual bool window_maximize_on_title_dbl_click() const override;
+ virtual bool window_minimize_on_title_dbl_click() const override;
+
virtual void cursor_set_shape(CursorShape p_shape) override;
virtual CursorShape cursor_get_shape() const override;
virtual void cursor_set_custom_image(const Ref &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
diff --git a/scene/gui/window_decoration.cpp b/scene/gui/window_decoration.cpp
new file mode 100644
index 000000000000..ce65e42e7c23
--- /dev/null
+++ b/scene/gui/window_decoration.cpp
@@ -0,0 +1,226 @@
+/**************************************************************************/
+/* window_decoration.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "window_decoration.h"
+
+#include "core/math/geometry_2d.h"
+#include "scene/main/viewport.h"
+#include "scene/main/window.h"
+
+void WindowDecoration::_update_rects() {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ return;
+ }
+#endif
+
+ 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 (const int &id : ids) {
+ DisplayServer::get_singleton()->window_remove_decoration(id, wid);
+ }
+ ids.clear();
+
+ Transform2D t = get_global_transform();
+ {
+ Vector polygon_global;
+ if (non_rect && polygon.size() >= 3) {
+ polygon_global = polygon;
+ } else {
+ polygon_global.push_back(Vector2());
+ polygon_global.push_back(Vector2(get_size().x, 0));
+ polygon_global.push_back(get_size());
+ polygon_global.push_back(Vector2(0, get_size().y));
+ }
+ for (Vector2 &E : polygon_global) {
+ E = t.xform(E);
+ }
+
+ int id = DisplayServer::get_singleton()->window_add_decoration(polygon_global, dec_type, wid);
+ ids.push_back(id);
+ }
+
+ int count = get_child_count();
+ for (int i = 0; i < count; i++) {
+ Control *n = Object::cast_to(get_child(i));
+ if (n && n->get_mouse_filter() != Control::MOUSE_FILTER_PASS) {
+ const Rect2 &rect = n->get_rect();
+
+ Vector 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));
+
+ for (Vector2 &E : polygon_global) {
+ E = t.xform(E);
+ }
+
+ int id = DisplayServer::get_singleton()->window_add_decoration(polygon_global, DisplayServer::WINDOW_DECORATION_PASS, wid);
+ ids.push_back(id);
+ }
+ }
+}
+
+void WindowDecoration::_global_transform_changed() {
+ _update_rects();
+}
+
+void WindowDecoration::add_child_notify(Node *p_child) {
+ Control::add_child_notify(p_child);
+
+ Control *control = Object::cast_to(p_child);
+ if (!control) {
+ return;
+ }
+
+ control->connect(SceneStringName(item_rect_changed), callable_mp(this, &WindowDecoration::_update_rects));
+
+ _update_rects();
+}
+
+void WindowDecoration::move_child_notify(Node *p_child) {
+ Control::move_child_notify(p_child);
+
+ if (!Object::cast_to(p_child)) {
+ return;
+ }
+
+ _update_rects();
+}
+
+void WindowDecoration::remove_child_notify(Node *p_child) {
+ Control::remove_child_notify(p_child);
+
+ Control *control = Object::cast_to(p_child);
+ if (!control) {
+ return;
+ }
+
+ control->disconnect(SceneStringName(item_rect_changed), callable_mp(this, &WindowDecoration::_update_rects));
+
+ _update_rects();
+}
+
+void WindowDecoration::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_non_rectangular_region", "non_rect"), &WindowDecoration::set_non_rectangular_region);
+ ClassDB::bind_method(D_METHOD("is_non_rectangular_region"), &WindowDecoration::is_non_rectangular_region);
+
+ ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &WindowDecoration::set_polygon);
+ ClassDB::bind_method(D_METHOD("get_polygon"), &WindowDecoration::get_polygon);
+
+ ClassDB::bind_method(D_METHOD("set_decoration_type", "pressed"), &WindowDecoration::set_decoration_type);
+ ClassDB::bind_method(D_METHOD("get_decoration_type"), &WindowDecoration::get_decoration_type);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "non_rectangular_region"), "set_non_rectangular_region", "is_non_rectangular_region");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "decoration_type", PROPERTY_HINT_ENUM, "Resize Top-Left,Resize Top,Resize Top-Right,Resize Left,Resize Right,Resize Bottom-Left,Resize Bottom,Resize Bottom-Right,Move"), "set_decoration_type", "get_decoration_type");
+}
+
+void WindowDecoration::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_RESIZED: {
+ _update_rects();
+ } break;
+
+ case NOTIFICATION_ENTER_TREE: {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ break;
+ }
+#endif
+ get_viewport()->connect("size_changed", callable_mp(this, &WindowDecoration::_update_rects));
+ _update_rects();
+ } break;
+
+ case NOTIFICATION_EXIT_TREE: {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ break;
+ }
+#endif
+ get_viewport()->disconnect("size_changed", callable_mp(this, &WindowDecoration::_update_rects));
+
+ if (!DisplayServer::get_singleton()) {
+ return;
+ }
+ DisplayServer::WindowID wid = get_viewport()->get_window_id();
+ for (const int &id : ids) {
+ DisplayServer::get_singleton()->window_remove_decoration(id, wid);
+ }
+ ids.clear();
+ } break;
+
+ default:
+ break;
+ }
+}
+
+void WindowDecoration::set_non_rectangular_region(bool p_non_rect) {
+ non_rect = p_non_rect;
+ _update_rects();
+}
+
+bool WindowDecoration::is_non_rectangular_region() const {
+ return non_rect;
+}
+
+void WindowDecoration::set_polygon(const Vector &p_polygon) {
+ polygon = p_polygon;
+ _update_rects();
+}
+
+Vector WindowDecoration::get_polygon() const {
+ return polygon;
+}
+
+void WindowDecoration::set_decoration_type(DisplayServer::WindowDecorationType p_dec_type) {
+ ERR_FAIL_INDEX(p_dec_type, DisplayServer::WINDOW_DECORATION_MAX);
+
+ if (dec_type == p_dec_type) {
+ return;
+ }
+
+ dec_type = p_dec_type;
+ _update_rects();
+}
+
+DisplayServer::WindowDecorationType WindowDecoration::get_decoration_type() const {
+ return dec_type;
+}
diff --git a/scene/gui/window_decoration.h b/scene/gui/window_decoration.h
new file mode 100644
index 000000000000..a8025b66af60
--- /dev/null
+++ b/scene/gui/window_decoration.h
@@ -0,0 +1,68 @@
+/**************************************************************************/
+/* window_decoration.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef WINDOW_DECORATION_H
+#define WINDOW_DECORATION_H
+
+#include "scene/gui/control.h"
+
+class WindowDecoration : public Control {
+ GDCLASS(WindowDecoration, Control);
+
+private:
+ DisplayServer::WindowDecorationType dec_type = DisplayServer::WINDOW_DECORATION_MOVE;
+ Vector ids;
+ bool non_rect = false;
+ Vector polygon;
+
+ void _update_rects();
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+ virtual void _global_transform_changed() override;
+
+ virtual void add_child_notify(Node *p_child) override;
+ virtual void move_child_notify(Node *p_child) override;
+ virtual void remove_child_notify(Node *p_child) override;
+
+public:
+ void set_decoration_type(DisplayServer::WindowDecorationType p_dec_type);
+ DisplayServer::WindowDecorationType get_decoration_type() const;
+
+ void set_non_rectangular_region(bool p_non_rect);
+ bool is_non_rectangular_region() const;
+
+ void set_polygon(const Vector &p_polygon);
+ Vector get_polygon() const;
+};
+
+#endif // WINDOW_DECORATION_H
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index f87dad1889b2..4dc166ebbd8a 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -980,6 +980,7 @@ void CanvasItem::_notify_transform(CanvasItem *p_node) {
}
_notify_transform(ci);
}
+ _global_transform_changed();
}
void CanvasItem::_physics_interpolated_changed() {
@@ -1122,6 +1123,11 @@ Vector2 CanvasItem::get_local_mouse_position() const {
return get_global_transform().affine_inverse().xform(get_global_mouse_position());
}
+Point2 CanvasItem::to_local(Point2 p_global) const {
+ ERR_READ_THREAD_GUARD_V(Point2());
+ return get_global_transform().affine_inverse().xform(p_global);
+}
+
void CanvasItem::force_update_transform() {
ERR_THREAD_GUARD;
ERR_FAIL_COND(!is_inside_tree());
diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h
index c74f8238e3c4..9d67a40e24fd 100644
--- a/scene/main/canvas_item.h
+++ b/scene/main/canvas_item.h
@@ -162,6 +162,8 @@ class CanvasItem : public Node {
}
}
+ virtual void _global_transform_changed() {}
+
void item_rect_changed(bool p_size_changed = true);
void _notification(int p_what);
@@ -364,6 +366,8 @@ class CanvasItem : public Node {
Vector2 get_global_mouse_position() const;
Vector2 get_local_mouse_position() const;
+ Point2 to_local(Point2 p_global) const;
+
void set_notify_local_transform(bool p_enable);
bool is_local_transform_notification_enabled() const;
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 8a048e9cc302..20229b958a71 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -91,6 +91,7 @@
#include "scene/gui/texture_rect.h"
#include "scene/gui/tree.h"
#include "scene/gui/video_stream_player.h"
+#include "scene/gui/window_decoration.h"
#include "scene/main/canvas_item.h"
#include "scene/main/canvas_layer.h"
#include "scene/main/http_request.h"
@@ -382,6 +383,7 @@ void register_scene_types() {
GDREGISTER_CLASS(CanvasModulate);
GDREGISTER_CLASS(ResourcePreloader);
GDREGISTER_CLASS(Window);
+ GDREGISTER_CLASS(WindowDecoration);
GDREGISTER_CLASS(StatusIndicator);
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 82ac62bc9fae..7816b7d848d4 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -601,6 +601,22 @@ void DisplayServer::window_set_mouse_passthrough(const Vector &p_region
ERR_FAIL_MSG("Mouse passthrough not supported by this display server.");
}
+int DisplayServer::window_add_decoration(const Vector &p_region, DisplayServer::WindowDecorationType p_dec_type, WindowID p_window) {
+ ERR_FAIL_V_MSG(-1, "Client side decorations are not supported by this display server.");
+}
+
+Array DisplayServer::window_get_decorations(WindowID p_window) const {
+ return Array();
+}
+
+void DisplayServer::window_change_decoration(int p_rect_id, const Vector &p_region, DisplayServer::WindowDecorationType p_dec_type, WindowID p_window) {
+ ERR_FAIL_MSG("Client side decorations are not supported by this display server.");
+}
+
+void DisplayServer::window_remove_decoration(int p_rect_id, WindowID p_window) {
+ ERR_FAIL_MSG("Client side decorations are not supported by this display server.");
+}
+
void DisplayServer::gl_window_make_current(DisplayServer::WindowID p_window_id) {
// noop except in gles
}
@@ -919,6 +935,11 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("window_get_title_size", "title", "window_id"), &DisplayServer::window_get_title_size, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_set_mouse_passthrough", "region", "window_id"), &DisplayServer::window_set_mouse_passthrough, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_add_decoration", "region", "dec_type", "window"), &DisplayServer::window_add_decoration, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_change_decoration", "id", "region", "dec_type", "window"), &DisplayServer::window_change_decoration, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_remove_decoration", "id", "window"), &DisplayServer::window_remove_decoration, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_get_decorations", "window"), &DisplayServer::window_get_decorations, DEFVAL(MAIN_WINDOW_ID));
+
ClassDB::bind_method(D_METHOD("window_get_current_screen", "window_id"), &DisplayServer::window_get_current_screen, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_set_current_screen", "screen", "window_id"), &DisplayServer::window_set_current_screen, DEFVAL(MAIN_WINDOW_ID));
@@ -1057,6 +1078,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_INPUT);
BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE);
BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE_EXTRA);
+ BIND_ENUM_CONSTANT(FEATURE_CLIENT_SIDE_DECORATIONS);
BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
@@ -1141,6 +1163,17 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(WINDOW_EVENT_DPI_CHANGE);
BIND_ENUM_CONSTANT(WINDOW_EVENT_TITLEBAR_CHANGE);
+ BIND_ENUM_CONSTANT(WINDOW_DECORATION_TOP_LEFT);
+ BIND_ENUM_CONSTANT(WINDOW_DECORATION_TOP);
+ BIND_ENUM_CONSTANT(WINDOW_DECORATION_TOP_RIGHT);
+ BIND_ENUM_CONSTANT(WINDOW_DECORATION_LEFT);
+ BIND_ENUM_CONSTANT(WINDOW_DECORATION_RIGHT);
+ BIND_ENUM_CONSTANT(WINDOW_DECORATION_BOTTOM_LEFT);
+ BIND_ENUM_CONSTANT(WINDOW_DECORATION_BOTTOM);
+ BIND_ENUM_CONSTANT(WINDOW_DECORATION_BOTTOM_RIGHT);
+ BIND_ENUM_CONSTANT(WINDOW_DECORATION_MOVE);
+ BIND_ENUM_CONSTANT(WINDOW_DECORATION_PASS);
+
BIND_ENUM_CONSTANT(VSYNC_DISABLED);
BIND_ENUM_CONSTANT(VSYNC_ENABLED);
BIND_ENUM_CONSTANT(VSYNC_ADAPTIVE);
diff --git a/servers/display_server.h b/servers/display_server.h
index 916c006f0113..95fc57bb0511 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -153,6 +153,7 @@ class DisplayServer : public Object {
FEATURE_NATIVE_DIALOG_INPUT,
FEATURE_NATIVE_DIALOG_FILE,
FEATURE_NATIVE_DIALOG_FILE_EXTRA,
+ FEATURE_CLIENT_SIDE_DECORATIONS,
};
virtual bool has_feature(Feature p_feature) const = 0;
@@ -440,6 +441,25 @@ class DisplayServer : public Object {
virtual void window_set_mouse_passthrough(const Vector &p_region, WindowID p_window = MAIN_WINDOW_ID);
+ enum WindowDecorationType {
+ WINDOW_DECORATION_TOP_LEFT,
+ WINDOW_DECORATION_TOP,
+ WINDOW_DECORATION_TOP_RIGHT,
+ WINDOW_DECORATION_LEFT,
+ WINDOW_DECORATION_RIGHT,
+ WINDOW_DECORATION_BOTTOM_LEFT,
+ WINDOW_DECORATION_BOTTOM,
+ WINDOW_DECORATION_BOTTOM_RIGHT,
+ WINDOW_DECORATION_MOVE,
+ WINDOW_DECORATION_PASS,
+ WINDOW_DECORATION_MAX,
+ };
+
+ virtual int window_add_decoration(const Vector &p_region, WindowDecorationType p_dec_type, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_change_decoration(int p_rect_id, const Vector &p_region, WindowDecorationType p_dec_type, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_remove_decoration(int p_rect_id, WindowID p_window = MAIN_WINDOW_ID);
+ virtual Array window_get_decorations(WindowID p_window = MAIN_WINDOW_ID) const;
+
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const = 0;
virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) = 0;
@@ -619,6 +639,7 @@ class DisplayServer : public Object {
};
VARIANT_ENUM_CAST(DisplayServer::WindowEvent)
+VARIANT_ENUM_CAST(DisplayServer::WindowDecorationType)
VARIANT_ENUM_CAST(DisplayServer::Feature)
VARIANT_ENUM_CAST(DisplayServer::MouseMode)
VARIANT_ENUM_CAST(DisplayServer::ScreenOrientation)