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)