diff options
Diffstat (limited to 'editor/plugins')
183 files changed, 35983 insertions, 20250 deletions
diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp index 348ef4ecc7..7c23e19564 100644 --- a/editor/plugins/abstract_polygon_2d_editor.cpp +++ b/editor/plugins/abstract_polygon_2d_editor.cpp @@ -1,39 +1,43 @@ -/*************************************************************************/ -/* abstract_polygon_2d_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* abstract_polygon_2d_editor.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 "abstract_polygon_2d_editor.h" #include "canvas_item_editor_plugin.h" #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "scene/gui/separator.h" bool AbstractPolygon2DEditor::Vertex::operator==(const AbstractPolygon2DEditor::Vertex &p_vertex) const { return polygon == p_vertex.polygon && vertex == p_vertex.vertex; @@ -87,6 +91,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(); + 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); } @@ -96,6 +101,7 @@ Vector2 AbstractPolygon2DEditor::_get_offset(int p_idx) const { } void AbstractPolygon2DEditor::_commit_action() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->add_do_method(canvas_item_editor, "update_viewport"); undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); undo_redo->commit_action(); @@ -147,12 +153,16 @@ void AbstractPolygon2DEditor::_menu_option(int p_option) { void AbstractPolygon2DEditor::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + button_create->set_icon(get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); + button_edit->set_icon(get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); + button_delete->set_icon(get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); + } break; + case NOTIFICATION_READY: { disable_polygon_editing(false, String()); - button_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); - button_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); - button_delete->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); button_edit->set_pressed(true); get_tree()->connect("node_removed", callable_mp(this, &AbstractPolygon2DEditor::_node_removed)); @@ -195,6 +205,7 @@ void AbstractPolygon2DEditor::_wip_close() { if (_is_line()) { _set_polygon(0, wip); } else if (wip.size() >= (_is_line() ? 2 : 3)) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Create Polygon")); _action_add_polygon(wip); if (_has_uv()) { @@ -227,13 +238,13 @@ void AbstractPolygon2DEditor::disable_polygon_editing(bool p_disable, String p_r button_delete->set_disabled(p_disable); if (p_disable) { - button_create->set_tooltip(p_reason); - button_edit->set_tooltip(p_reason); - button_delete->set_tooltip(p_reason); + button_create->set_tooltip_text(p_reason); + button_edit->set_tooltip_text(p_reason); + button_delete->set_tooltip_text(p_reason); } else { - button_create->set_tooltip(TTR("Create points.")); - button_edit->set_tooltip(TTR("Edit points.\nLMB: Move Point\nRMB: Erase Point")); - button_delete->set_tooltip(TTR("Erase points.")); + button_create->set_tooltip_text(TTR("Create points.")); + button_edit->set_tooltip_text(TTR("Edit points.\nLMB: Move Point\nRMB: Erase Point")); + button_delete->set_tooltip_text(TTR("Erase points.")); } } @@ -242,6 +253,11 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) return false; } + if (!_get_node()->is_visible_in_tree()) { + return false; + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); Ref<InputEventMouseButton> mb = p_event; if (!_has_resource()) { @@ -283,15 +299,14 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) _commit_action(); return true; } else { - Vector<Vector2> vertices2 = _get_polygon(insert.polygon); - pre_move_edit = vertices2; edited_point = PosVertex(insert.polygon, insert.vertex + 1, xform.affine_inverse().xform(insert.pos)); - vertices2.insert(edited_point.vertex, edited_point.pos); + vertices.insert(edited_point.vertex, edited_point.pos); + pre_move_edit = vertices; selected_point = Vertex(edited_point.polygon, edited_point.vertex); edge_point = PosVertex(); undo_redo->create_action(TTR("Insert Point")); - _action_set_polygon(insert.polygon, vertices2); + _action_set_polygon(insert.polygon, vertices); _commit_action(); return true; } @@ -395,7 +410,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) if (mm.is_valid()) { Vector2 gpoint = mm->get_position(); - if (edited_point.valid() && (wip_active || (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE)) { + if (edited_point.valid() && (wip_active || mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { Vector2 cpoint = _get_node()->to_local(canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint))); //Move the point in a single axis. Should only work when editing a polygon and while holding shift. @@ -475,6 +490,10 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl return; } + if (!_get_node()->is_visible_in_tree()) { + return; + } + Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_global_transform(); // All polygon points are sharp, so use the sharp handle icon const Ref<Texture2D> handle = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons")); @@ -546,15 +565,19 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl const Vector2 p = (vertex == edited_point) ? edited_point.pos : (points[i] + offset); const Vector2 point = xform.xform(p); - const Color modulate = vertex == active_point ? Color(0.5, 1, 2) : Color(1, 1, 1); - p_overlay->draw_texture(handle, point - handle->get_size() * 0.5, modulate); + const Color overlay_modulate = vertex == active_point ? Color(0.5, 1, 2) : Color(1, 1, 1); + p_overlay->draw_texture(handle, point - handle->get_size() * 0.5, overlay_modulate); if (vertex == hover_point) { - Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); - int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); + Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); String num = String::num(vertex.vertex); - Size2 num_size = font->get_string_size(num, font_size); - p_overlay->draw_string(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5)); + Size2 num_size = font->get_string_size(num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); + const float outline_size = 4; + Color font_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); + Color outline_color = font_color.inverted(); + p_overlay->draw_string_outline(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + p_overlay->draw_string(font, point - num_size * 0.5, num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); } } } @@ -596,6 +619,7 @@ void AbstractPolygon2DEditor::_bind_methods() { } void AbstractPolygon2DEditor::remove_point(const Vertex &p_vertex) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); Vector<Vector2> vertices = _get_polygon(p_vertex.polygon); if (vertices.size() > (_is_line() ? 2 : 3)) { @@ -690,12 +714,7 @@ AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_edge_point(c return closest; } -AbstractPolygon2DEditor::AbstractPolygon2DEditor(EditorNode *p_editor, bool p_wip_destructive) { - canvas_item_editor = nullptr; - editor = p_editor; - undo_redo = EditorNode::get_undo_redo(); - - wip_active = false; +AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) { edited_point = PosVertex(); wip_destructive = p_wip_destructive; @@ -707,26 +726,24 @@ AbstractPolygon2DEditor::AbstractPolygon2DEditor(EditorNode *p_editor, bool p_wi button_create = memnew(Button); button_create->set_flat(true); add_child(button_create); - button_create->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option), varray(MODE_CREATE)); + button_create->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_CREATE)); button_create->set_toggle_mode(true); button_edit = memnew(Button); button_edit->set_flat(true); add_child(button_edit); - button_edit->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option), varray(MODE_EDIT)); + button_edit->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_EDIT)); button_edit->set_toggle_mode(true); button_delete = memnew(Button); button_delete->set_flat(true); add_child(button_delete); - button_delete->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option), varray(MODE_DELETE)); + button_delete->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_DELETE)); button_delete->set_toggle_mode(true); create_resource = memnew(ConfirmationDialog); add_child(create_resource); - create_resource->get_ok_button()->set_text(TTR("Create")); - - mode = MODE_EDIT; + create_resource->set_ok_button_text(TTR("Create")); } void AbstractPolygon2DEditorPlugin::edit(Object *p_object) { @@ -746,9 +763,8 @@ void AbstractPolygon2DEditorPlugin::make_visible(bool p_visible) { } } -AbstractPolygon2DEditorPlugin::AbstractPolygon2DEditorPlugin(EditorNode *p_node, AbstractPolygon2DEditor *p_polygon_editor, String p_class) : +AbstractPolygon2DEditorPlugin::AbstractPolygon2DEditorPlugin(AbstractPolygon2DEditor *p_polygon_editor, String p_class) : polygon_editor(p_polygon_editor), - editor(p_node), klass(p_class) { CanvasItemEditor::get_singleton()->add_control_to_menu_panel(polygon_editor); polygon_editor->hide(); diff --git a/editor/plugins/abstract_polygon_2d_editor.h b/editor/plugins/abstract_polygon_2d_editor.h index 8db5bf58dd..832972c398 100644 --- a/editor/plugins/abstract_polygon_2d_editor.h +++ b/editor/plugins/abstract_polygon_2d_editor.h @@ -1,48 +1,49 @@ -/*************************************************************************/ -/* abstract_polygon_2d_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* abstract_polygon_2d_editor.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 ABSTRACT_POLYGON_2D_EDITOR_H #define ABSTRACT_POLYGON_2D_EDITOR_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/2d/polygon_2d.h" +#include "scene/gui/box_container.h" class CanvasItemEditor; +class ConfirmationDialog; class AbstractPolygon2DEditor : public HBoxContainer { GDCLASS(AbstractPolygon2DEditor, HBoxContainer); - Button *button_create; - Button *button_edit; - Button *button_delete; + Button *button_create = nullptr; + Button *button_edit = nullptr; + Button *button_delete = nullptr; struct Vertex { Vertex() {} @@ -80,15 +81,14 @@ class AbstractPolygon2DEditor : public HBoxContainer { Vector<Vector2> pre_move_edit; Vector<Vector2> wip; - bool wip_active; - bool wip_destructive; + bool wip_active = false; + bool wip_destructive = false; - bool _polygon_editing_enabled; + bool _polygon_editing_enabled = false; - CanvasItemEditor *canvas_item_editor; - EditorNode *editor; - Panel *panel; - ConfirmationDialog *create_resource; + CanvasItemEditor *canvas_item_editor = nullptr; + Panel *panel = nullptr; + ConfirmationDialog *create_resource = nullptr; protected: enum { @@ -98,9 +98,7 @@ protected: MODE_CONT, }; - int mode; - - UndoRedo *undo_redo; + int mode = MODE_EDIT; virtual void _menu_option(int p_option); void _wip_changed(); @@ -144,14 +142,13 @@ public: void forward_canvas_draw_over_viewport(Control *p_overlay); void edit(Node *p_polygon); - AbstractPolygon2DEditor(EditorNode *p_editor, bool p_wip_destructive = true); + AbstractPolygon2DEditor(bool p_wip_destructive = true); }; class AbstractPolygon2DEditorPlugin : public EditorPlugin { GDCLASS(AbstractPolygon2DEditorPlugin, EditorPlugin); - AbstractPolygon2DEditor *polygon_editor; - EditorNode *editor; + AbstractPolygon2DEditor *polygon_editor = nullptr; String klass; public: @@ -164,7 +161,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - AbstractPolygon2DEditorPlugin(EditorNode *p_node, AbstractPolygon2DEditor *p_polygon_editor, String p_class); + AbstractPolygon2DEditorPlugin(AbstractPolygon2DEditor *p_polygon_editor, String p_class); ~AbstractPolygon2DEditorPlugin(); }; diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp index 3dcb769faf..df94815105 100644 --- a/editor/plugins/animation_blend_space_1d_editor.cpp +++ b/editor/plugins/animation_blend_space_1d_editor.cpp @@ -1,38 +1,45 @@ -/*************************************************************************/ -/* animation_blend_space_1d_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_blend_space_1d_editor.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 "animation_blend_space_1d_editor.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "scene/animation/animation_blend_tree.h" +#include "scene/gui/check_box.h" +#include "scene/gui/option_button.h" +#include "scene/gui/panel_container.h" StringName AnimationNodeBlendSpace1DEditor::get_blend_position_path() const { StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + "blend_position"; @@ -40,11 +47,18 @@ StringName AnimationNodeBlendSpace1DEditor::get_blend_position_path() const { } void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEvent> &p_event) { + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } + Ref<InputEventKey> k = p_event; if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && !k->is_echo()) { if (selected_point != -1) { - _erase_selected(); + if (!read_only) { + _erase_selected(); + } accept_event(); } } @@ -52,67 +66,66 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) || (mb->get_button_index() == MouseButton::LEFT && tool_create->is_pressed()))) { - menu->clear(); - animations_menu->clear(); - animations_to_add.clear(); - - List<StringName> classes; - ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); - classes.sort_custom<StringName::AlphCompare>(); + if (!read_only) { + menu->clear(); + animations_menu->clear(); + animations_to_add.clear(); - menu->add_submenu_item(TTR("Add Animation"), "animations"); + List<StringName> classes; + ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); + classes.sort_custom<StringName::AlphCompare>(); - AnimationTree *gp = AnimationTreeEditor::get_singleton()->get_tree(); - ERR_FAIL_COND(!gp); + menu->add_submenu_item(TTR("Add Animation"), "animations"); - if (gp->has_node(gp->get_animation_player())) { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player())); + if (tree->has_node(tree->get_animation_player())) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(tree->get_node(tree->get_animation_player())); - if (ap) { - List<StringName> names; - ap->get_animation_list(&names); + if (ap) { + List<StringName> names; + ap->get_animation_list(&names); - for (const StringName &E : names) { - animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E); - animations_to_add.push_back(E); + for (const StringName &E : names) { + animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E); + animations_to_add.push_back(E); + } } } - } - for (const StringName &E : classes) { - String name = String(E).replace_first("AnimationNode", ""); - if (name == "Animation") { - continue; - } + for (const StringName &E : classes) { + String name = String(E).replace_first("AnimationNode", ""); + if (name == "Animation" || name == "StartState" || name == "EndState") { + continue; + } - int idx = menu->get_item_count(); - menu->add_item(vformat("Add %s", name), idx); - menu->set_item_metadata(idx, E); - } + int idx = menu->get_item_count(); + menu->add_item(vformat(TTR("Add %s"), name), idx); + menu->set_item_metadata(idx, E); + } - Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard(); - if (clipb.is_valid()) { + Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard(); + if (clipb.is_valid()) { + menu->add_separator(); + menu->add_item(TTR("Paste"), MENU_PASTE); + } menu->add_separator(); - menu->add_item(TTR("Paste"), MENU_PASTE); - } - menu->add_separator(); - menu->add_item(TTR("Load..."), MENU_LOAD_FILE); + menu->add_item(TTR("Load..."), MENU_LOAD_FILE); - menu->set_position(blend_space_draw->get_screen_position() + mb->get_position()); - menu->reset_size(); - menu->popup(); + menu->set_position(blend_space_draw->get_screen_position() + mb->get_position()); + menu->reset_size(); + menu->popup(); - add_point_pos = (mb->get_position() / blend_space_draw->get_size()).x; - add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); - add_point_pos += blend_space->get_min_space(); + add_point_pos = (mb->get_position() / blend_space_draw->get_size()).x; + add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); + add_point_pos += blend_space->get_min_space(); - if (snap->is_pressed()) { - add_point_pos = Math::snapped(add_point_pos, blend_space->get_snap()); + if (snap->is_pressed()) { + add_point_pos = Math::snapped(add_point_pos, blend_space->get_snap()); + } } } if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { - blend_space_draw->update(); // why not + blend_space_draw->queue_redraw(); // why not // try to see if a point can be selected selected_point = -1; @@ -134,31 +147,34 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven } if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == MouseButton::LEFT) { - if (dragging_selected) { - // move - float point = blend_space->get_blend_point_position(selected_point); - point += drag_ofs.x; + if (!read_only) { + if (dragging_selected) { + // move + float point = blend_space->get_blend_point_position(selected_point); + point += drag_ofs.x; + + if (snap->is_pressed()) { + point = Math::snapped(point, blend_space->get_snap()); + } - if (snap->is_pressed()) { - point = Math::snapped(point, blend_space->get_snap()); + updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Move Node Point")); + undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point); + undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->add_do_method(this, "_update_edited_point_pos"); + undo_redo->add_undo_method(this, "_update_edited_point_pos"); + undo_redo->commit_action(); + updating = false; + _update_edited_point_pos(); } - updating = true; - undo_redo->create_action(TTR("Move Node Point")); - undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point); - undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); - undo_redo->add_do_method(this, "_update_space"); - undo_redo->add_undo_method(this, "_update_space"); - undo_redo->add_do_method(this, "_update_edited_point_pos"); - undo_redo->add_undo_method(this, "_update_edited_point_pos"); - undo_redo->commit_action(); - updating = false; - _update_edited_point_pos(); + dragging_selected_attempt = false; + dragging_selected = false; + blend_space_draw->queue_redraw(); } - - dragging_selected_attempt = false; - dragging_selected = false; - blend_space_draw->update(); } // *set* the blend @@ -167,36 +183,41 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven blend_pos *= blend_space->get_max_space() - blend_space->get_min_space(); blend_pos += blend_space->get_min_space(); - AnimationTreeEditor::get_singleton()->get_tree()->set(get_blend_position_path(), blend_pos); - blend_space_draw->update(); + tree->set(get_blend_position_path(), blend_pos); + blend_space_draw->queue_redraw(); } Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid() && !blend_space_draw->has_focus()) { blend_space_draw->grab_focus(); - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } if (mm.is_valid() && dragging_selected_attempt) { dragging_selected = true; drag_ofs = ((mm->get_position() - drag_from) / blend_space_draw->get_size()) * ((blend_space->get_max_space() - blend_space->get_min_space()) * Vector2(1, 0)); - blend_space_draw->update(); + blend_space_draw->queue_redraw(); _update_edited_point_pos(); } - if (mm.is_valid() && tool_blend->is_pressed() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { + if (mm.is_valid() && tool_blend->is_pressed() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { float blend_pos = mm->get_position().x / blend_space_draw->get_size().x; blend_pos *= blend_space->get_max_space() - blend_space->get_min_space(); blend_pos += blend_space->get_min_space(); - AnimationTreeEditor::get_singleton()->get_tree()->set(get_blend_position_path(), blend_pos); + tree->set(get_blend_position_path(), blend_pos); - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } } void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } + Color linecolor = get_theme_color(SNAME("font_color"), SNAME("Label")); Color linecolor_soft = linecolor; linecolor_soft.a *= 0.5; @@ -213,7 +234,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { blend_space_draw->draw_rect(Rect2(Point2(), s), color, false); } - blend_space_draw->draw_line(Point2(1, s.height - 1), Point2(s.width - 1, s.height - 1), linecolor); + blend_space_draw->draw_line(Point2(1, s.height - 1), Point2(s.width - 1, s.height - 1), linecolor, Math::round(EDSCALE)); if (blend_space->get_min_space() < 0) { float point = 0.0; @@ -222,9 +243,9 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { float x = point; - blend_space_draw->draw_line(Point2(x, s.height - 1), Point2(x, s.height - 5 * EDSCALE), linecolor); + blend_space_draw->draw_line(Point2(x, s.height - 1), Point2(x, s.height - 5 * EDSCALE), linecolor, Math::round(EDSCALE)); blend_space_draw->draw_string(font, Point2(x + 2 * EDSCALE, s.height - 2 * EDSCALE - font->get_height(font_size) + font->get_ascent(font_size)), "0", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, linecolor); - blend_space_draw->draw_line(Point2(x, s.height - 5 * EDSCALE), Point2(x, 0), linecolor_soft); + blend_space_draw->draw_line(Point2(x, s.height - 5 * EDSCALE), Point2(x, 0), linecolor_soft, Math::round(EDSCALE)); } if (snap->is_pressed()) { @@ -238,7 +259,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { int idx = int(v / blend_space->get_snap()); if (i > 0 && prev_idx != idx) { - blend_space_draw->draw_line(Point2(i, 0), Point2(i, s.height), linecolor_soft); + blend_space_draw->draw_line(Point2(i, 0), Point2(i, s.height), linecolor_soft, Math::round(EDSCALE)); } prev_idx = idx; @@ -251,10 +272,12 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { for (int i = 0; i < blend_space->get_blend_point_count(); i++) { float point = blend_space->get_blend_point_position(i); - if (dragging_selected && selected_point == i) { - point += drag_ofs.x; - if (snap->is_pressed()) { - point = Math::snapped(point, blend_space->get_snap()); + if (!read_only) { + if (dragging_selected && selected_point == i) { + point += drag_ofs.x; + if (snap->is_pressed()) { + point = Math::snapped(point, blend_space->get_snap()); + } } } @@ -286,7 +309,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { color.a *= 0.5; } - float point = AnimationTreeEditor::get_singleton()->get_tree()->get(get_blend_position_path()); + float point = tree->get(get_blend_position_path()); point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); point *= s.width; @@ -295,10 +318,10 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { float mind = 5 * EDSCALE; float maxd = 15 * EDSCALE; - blend_space_draw->draw_line(gui_point + Vector2(mind, 0), gui_point + Vector2(maxd, 0), color, 2); - blend_space_draw->draw_line(gui_point + Vector2(-mind, 0), gui_point + Vector2(-maxd, 0), color, 2); - blend_space_draw->draw_line(gui_point + Vector2(0, mind), gui_point + Vector2(0, maxd), color, 2); - blend_space_draw->draw_line(gui_point + Vector2(0, -mind), gui_point + Vector2(0, -maxd), color, 2); + blend_space_draw->draw_line(gui_point + Vector2(mind, 0), gui_point + Vector2(maxd, 0), color, Math::round(2 * EDSCALE)); + blend_space_draw->draw_line(gui_point + Vector2(-mind, 0), gui_point + Vector2(-maxd, 0), color, Math::round(2 * EDSCALE)); + blend_space_draw->draw_line(gui_point + Vector2(0, mind), gui_point + Vector2(0, maxd), color, Math::round(2 * EDSCALE)); + blend_space_draw->draw_line(gui_point + Vector2(0, -mind), gui_point + Vector2(0, -maxd), color, Math::round(2 * EDSCALE)); } } @@ -312,11 +335,14 @@ void AnimationNodeBlendSpace1DEditor::_update_space() { max_value->set_value(blend_space->get_max_space()); min_value->set_value(blend_space->get_min_space()); + sync->set_pressed(blend_space->is_using_sync()); + interpolation->select(blend_space->get_blend_mode()); + label_value->set_text(blend_space->get_value_label()); snap_value->set_value(blend_space->get_snap()); - blend_space_draw->update(); + blend_space_draw->queue_redraw(); updating = false; } @@ -327,19 +353,24 @@ void AnimationNodeBlendSpace1DEditor::_config_changed(double) { } updating = true; - undo_redo->create_action(TTR("Change BlendSpace1D Limits")); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Change BlendSpace1D Config")); undo_redo->add_do_method(blend_space.ptr(), "set_max_space", max_value->get_value()); undo_redo->add_undo_method(blend_space.ptr(), "set_max_space", blend_space->get_max_space()); undo_redo->add_do_method(blend_space.ptr(), "set_min_space", min_value->get_value()); undo_redo->add_undo_method(blend_space.ptr(), "set_min_space", blend_space->get_min_space()); undo_redo->add_do_method(blend_space.ptr(), "set_snap", snap_value->get_value()); undo_redo->add_undo_method(blend_space.ptr(), "set_snap", blend_space->get_snap()); + undo_redo->add_do_method(blend_space.ptr(), "set_use_sync", sync->is_pressed()); + undo_redo->add_undo_method(blend_space.ptr(), "set_use_sync", blend_space->is_using_sync()); + undo_redo->add_do_method(blend_space.ptr(), "set_blend_mode", interpolation->get_selected()); + undo_redo->add_undo_method(blend_space.ptr(), "set_blend_mode", blend_space->get_blend_mode()); undo_redo->add_do_method(this, "_update_space"); undo_redo->add_undo_method(this, "_update_space"); undo_redo->commit_action(); updating = false; - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace1DEditor::_labels_changed(String) { @@ -348,6 +379,7 @@ void AnimationNodeBlendSpace1DEditor::_labels_changed(String) { } updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Change BlendSpace1D Labels"), UndoRedo::MERGE_ENDS); undo_redo->add_do_method(blend_space.ptr(), "set_value_label", label_value->get_text()); undo_redo->add_undo_method(blend_space.ptr(), "set_value_label", blend_space->get_value_label()); @@ -358,13 +390,15 @@ void AnimationNodeBlendSpace1DEditor::_labels_changed(String) { } void AnimationNodeBlendSpace1DEditor::_snap_toggled() { - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace1DEditor::_file_opened(const String &p_file) { file_loaded = ResourceLoader::load(p_file); if (file_loaded.is_valid()) { _add_menu_type(MENU_LOAD_FILE_CONFIRM); + } else { + EditorNode::get_singleton()->show_warning(TTR("This type of node can't be used. Only animation nodes are allowed.")); } } @@ -401,6 +435,7 @@ void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) { } updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Node Point")); undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", node, add_point_pos); undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count()); @@ -409,7 +444,7 @@ void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) { undo_redo->commit_action(); updating = false; - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace1DEditor::_add_animation_type(int p_index) { @@ -419,6 +454,7 @@ void AnimationNodeBlendSpace1DEditor::_add_animation_type(int p_index) { anim->set_animation(animations_to_add[p_index]); updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Animation Point")); undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", anim, add_point_pos); undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count()); @@ -427,7 +463,7 @@ void AnimationNodeBlendSpace1DEditor::_add_animation_type(int p_index) { undo_redo->commit_action(); updating = false; - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace1DEditor::_tool_switch(int p_tool) { @@ -440,7 +476,7 @@ void AnimationNodeBlendSpace1DEditor::_tool_switch(int p_tool) { } _update_tool_erase(); - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace1DEditor::_update_edited_point_pos() { @@ -467,7 +503,7 @@ void AnimationNodeBlendSpace1DEditor::_update_edited_point_pos() { void AnimationNodeBlendSpace1DEditor::_update_tool_erase() { bool point_valid = selected_point >= 0 && selected_point < blend_space->get_blend_point_count(); - tool_erase->set_disabled(!point_valid); + tool_erase->set_disabled(!point_valid || read_only); if (point_valid) { Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point); @@ -478,7 +514,11 @@ void AnimationNodeBlendSpace1DEditor::_update_tool_erase() { open_editor->hide(); } - edit_hb->show(); + if (!read_only) { + edit_hb->show(); + } else { + edit_hb->hide(); + } } else { edit_hb->hide(); } @@ -488,6 +528,7 @@ void AnimationNodeBlendSpace1DEditor::_erase_selected() { if (selected_point != -1) { updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove BlendSpace1D Point")); undo_redo->add_do_method(blend_space.ptr(), "remove_blend_point", selected_point); undo_redo->add_undo_method(blend_space.ptr(), "add_blend_point", blend_space->get_blend_point_node(selected_point), blend_space->get_blend_point_position(selected_point), selected_point); @@ -497,7 +538,7 @@ void AnimationNodeBlendSpace1DEditor::_erase_selected() { updating = false; - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } } @@ -507,6 +548,7 @@ void AnimationNodeBlendSpace1DEditor::_edit_point_pos(double) { } updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Move BlendSpace1D Node Point")); undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, edit_value->get_value()); undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); @@ -517,7 +559,7 @@ void AnimationNodeBlendSpace1DEditor::_edit_point_pos(double) { undo_redo->commit_action(); updating = false; - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace1DEditor::_open_editor() { @@ -529,39 +571,51 @@ void AnimationNodeBlendSpace1DEditor::_open_editor() { } void AnimationNodeBlendSpace1DEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - tool_blend->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); - tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); - tool_create->set_icon(get_theme_icon(SNAME("EditKey"), SNAME("EditorIcons"))); - tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - snap->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); - open_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); - } + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + tool_blend->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); + tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_create->set_icon(get_theme_icon(SNAME("EditKey"), SNAME("EditorIcons"))); + tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + snap->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + open_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + interpolation->clear(); + interpolation->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), "", 0); + interpolation->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), "", 1); + interpolation->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), "", 2); + } break; + + case NOTIFICATION_PROCESS: { + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } - if (p_what == NOTIFICATION_PROCESS) { - String error; + String error; - if (!AnimationTreeEditor::get_singleton()->get_tree()->is_active()) { - error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); - } else if (AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { - error = AnimationTreeEditor::get_singleton()->get_tree()->get_invalid_state_reason(); - } + if (!tree->is_active()) { + error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); + } else if (tree->is_state_invalid()) { + error = tree->get_invalid_state_reason(); + } - if (error != error_label->get_text()) { - error_label->set_text(error); - if (!error.is_empty()) { - error_panel->show(); - } else { - error_panel->hide(); + if (error != error_label->get_text()) { + error_label->set_text(error); + if (!error.is_empty()) { + error_panel->show(); + } else { + error_panel->hide(); + } } - } - } + } break; - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - set_process(is_visible_in_tree()); + case NOTIFICATION_VISIBILITY_CHANGED: { + set_process(is_visible_in_tree()); + } break; } } @@ -579,17 +633,27 @@ bool AnimationNodeBlendSpace1DEditor::can_edit(const Ref<AnimationNode> &p_node) void AnimationNodeBlendSpace1DEditor::edit(const Ref<AnimationNode> &p_node) { blend_space = p_node; + read_only = false; if (!blend_space.is_null()) { + read_only = EditorNode::get_singleton()->is_resource_read_only(blend_space); + _update_space(); } + + tool_create->set_disabled(read_only); + edit_value->set_editable(!read_only); + label_value->set_editable(!read_only); + min_value->set_editable(!read_only); + max_value->set_editable(!read_only); + sync->set_disabled(read_only); + interpolation->set_disabled(read_only); } AnimationNodeBlendSpace1DEditor *AnimationNodeBlendSpace1DEditor::singleton = nullptr; AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { singleton = this; - updating = false; HBoxContainer *top_hb = memnew(HBoxContainer); add_child(top_hb); @@ -603,31 +667,31 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { tool_blend->set_button_group(bg); top_hb->add_child(tool_blend); tool_blend->set_pressed(true); - tool_blend->set_tooltip(TTR("Set the blending position within the space")); - tool_blend->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch), varray(3)); + tool_blend->set_tooltip_text(TTR("Set the blending position within the space")); + tool_blend->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch).bind(3)); tool_select = memnew(Button); tool_select->set_flat(true); tool_select->set_toggle_mode(true); tool_select->set_button_group(bg); top_hb->add_child(tool_select); - tool_select->set_tooltip(TTR("Select and move points, create points with RMB.")); - tool_select->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch), varray(0)); + tool_select->set_tooltip_text(TTR("Select and move points, create points with RMB.")); + tool_select->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch).bind(0)); tool_create = memnew(Button); tool_create->set_flat(true); tool_create->set_toggle_mode(true); tool_create->set_button_group(bg); top_hb->add_child(tool_create); - tool_create->set_tooltip(TTR("Create points.")); - tool_create->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch), varray(1)); + tool_create->set_tooltip_text(TTR("Create points.")); + tool_create->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch).bind(1)); tool_erase_sep = memnew(VSeparator); top_hb->add_child(tool_erase_sep); tool_erase = memnew(Button); tool_erase->set_flat(true); top_hb->add_child(tool_erase); - tool_erase->set_tooltip(TTR("Erase points.")); + tool_erase->set_tooltip_text(TTR("Erase points.")); tool_erase->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_erase_selected)); top_hb->add_child(memnew(VSeparator)); @@ -637,7 +701,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { snap->set_toggle_mode(true); top_hb->add_child(snap); snap->set_pressed(true); - snap->set_tooltip(TTR("Enable snap and show grid.")); + snap->set_tooltip_text(TTR("Enable snap and show grid.")); snap->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_snap_toggled)); snap_value = memnew(SpinBox); @@ -646,6 +710,19 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { snap_value->set_step(0.01); snap_value->set_max(1000); + top_hb->add_child(memnew(VSeparator)); + top_hb->add_child(memnew(Label(TTR("Sync:")))); + sync = memnew(CheckBox); + top_hb->add_child(sync); + sync->connect("toggled", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed)); + + top_hb->add_child(memnew(VSeparator)); + + top_hb->add_child(memnew(Label(TTR("Blend:")))); + interpolation = memnew(OptionButton); + top_hb->add_child(interpolation); + interpolation->connect("item_selected", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed)); + edit_hb = memnew(HBoxContainer); top_hb->add_child(edit_hb); edit_hb->add_child(memnew(VSeparator)); @@ -661,7 +738,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { open_editor = memnew(Button); edit_hb->add_child(open_editor); open_editor->set_text(TTR("Open Editor")); - open_editor->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_open_editor), varray(), CONNECT_DEFERRED); + open_editor->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_open_editor), CONNECT_DEFERRED); edit_hb->hide(); open_editor->hide(); @@ -722,8 +799,6 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { error_panel->add_child(error_label); error_label->set_text("hmmm"); - undo_redo = EditorNode::get_undo_redo(); - menu = memnew(PopupMenu); add_child(menu); menu->connect("id_pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_add_menu_type)); @@ -738,11 +813,6 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { open_file->set_title(TTR("Open Animation Node")); open_file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); open_file->connect("file_selected", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_file_opened)); - undo_redo = EditorNode::get_undo_redo(); - - selected_point = -1; - dragging_selected = false; - dragging_selected_attempt = false; set_custom_minimum_size(Size2(0, 150 * EDSCALE)); } diff --git a/editor/plugins/animation_blend_space_1d_editor.h b/editor/plugins/animation_blend_space_1d_editor.h index 7906395c8f..90104fc310 100644 --- a/editor/plugins/animation_blend_space_1d_editor.h +++ b/editor/plugins/animation_blend_space_1d_editor.h @@ -1,81 +1,86 @@ -/*************************************************************************/ -/* animation_blend_space_1d_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_blend_space_1d_editor.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 ANIMATION_BLEND_SPACE_1D_EDITOR_H #define ANIMATION_BLEND_SPACE_1D_EDITOR_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/plugins/animation_tree_editor_plugin.h" -#include "editor/property_editor.h" #include "scene/animation/animation_blend_space_1d.h" #include "scene/gui/button.h" #include "scene/gui/graph_edit.h" #include "scene/gui/popup.h" +#include "scene/gui/separator.h" #include "scene/gui/tree.h" +class CheckBox; +class OptionButton; +class PanelContainer; + class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeBlendSpace1DEditor, AnimationTreeNodeEditorPlugin); Ref<AnimationNodeBlendSpace1D> blend_space; + bool read_only = false; - HBoxContainer *goto_parent_hb; - Button *goto_parent; + HBoxContainer *goto_parent_hb = nullptr; + Button *goto_parent = nullptr; - PanelContainer *panel; - Button *tool_blend; - Button *tool_select; - Button *tool_create; - VSeparator *tool_erase_sep; - Button *tool_erase; - Button *snap; - SpinBox *snap_value; + PanelContainer *panel = nullptr; + Button *tool_blend = nullptr; + Button *tool_select = nullptr; + Button *tool_create = nullptr; + VSeparator *tool_erase_sep = nullptr; + Button *tool_erase = nullptr; + Button *snap = nullptr; + SpinBox *snap_value = nullptr; - LineEdit *label_value; - SpinBox *max_value; - SpinBox *min_value; + LineEdit *label_value = nullptr; + SpinBox *max_value = nullptr; + SpinBox *min_value = nullptr; - HBoxContainer *edit_hb; - SpinBox *edit_value; - Button *open_editor; + CheckBox *sync = nullptr; + OptionButton *interpolation = nullptr; - int selected_point; + HBoxContainer *edit_hb = nullptr; + SpinBox *edit_value = nullptr; + Button *open_editor = nullptr; - Control *blend_space_draw; + int selected_point = -1; - PanelContainer *error_panel; - Label *error_label; + Control *blend_space_draw = nullptr; - bool updating; + PanelContainer *error_panel = nullptr; + Label *error_label = nullptr; - UndoRedo *undo_redo; + bool updating = false; static AnimationNodeBlendSpace1DEditor *singleton; @@ -88,14 +93,14 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { void _labels_changed(String); void _snap_toggled(); - PopupMenu *menu; - PopupMenu *animations_menu; + PopupMenu *menu = nullptr; + PopupMenu *animations_menu = nullptr; Vector<String> animations_to_add; - float add_point_pos; + float add_point_pos = 0.0f; Vector<real_t> points; - bool dragging_selected_attempt; - bool dragging_selected; + bool dragging_selected_attempt = false; + bool dragging_selected = false; Vector2 drag_from; Vector2 drag_ofs; @@ -109,7 +114,7 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { void _edit_point_pos(double); void _open_editor(); - EditorFileDialog *open_file; + EditorFileDialog *open_file = nullptr; Ref<AnimationNode> file_loaded; void _file_opened(const String &p_file); diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index 459de5d35b..0daf934e17 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* animation_blend_space_2d_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_blend_space_2d_editor.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 "animation_blend_space_2d_editor.h" @@ -35,11 +35,19 @@ #include "core/io/resource_loader.h" #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "scene/animation/animation_blend_tree.h" #include "scene/animation/animation_player.h" +#include "scene/gui/check_box.h" +#include "scene/gui/grid_container.h" #include "scene/gui/menu_button.h" +#include "scene/gui/option_button.h" #include "scene/gui/panel.h" +#include "scene/gui/panel_container.h" #include "scene/main/window.h" bool AnimationNodeBlendSpace2DEditor::can_edit(const Ref<AnimationNode> &p_node) { @@ -48,7 +56,7 @@ bool AnimationNodeBlendSpace2DEditor::can_edit(const Ref<AnimationNode> &p_node) } void AnimationNodeBlendSpace2DEditor::_blend_space_changed() { - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace2DEditor::edit(const Ref<AnimationNode> &p_node) { @@ -56,11 +64,28 @@ void AnimationNodeBlendSpace2DEditor::edit(const Ref<AnimationNode> &p_node) { blend_space->disconnect("triangles_updated", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_blend_space_changed)); } blend_space = p_node; + read_only = false; if (!blend_space.is_null()) { + read_only = EditorNode::get_singleton()->is_resource_read_only(blend_space); + blend_space->connect("triangles_updated", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_blend_space_changed)); _update_space(); } + + tool_create->set_disabled(read_only); + max_x_value->set_editable(!read_only); + min_x_value->set_editable(!read_only); + max_y_value->set_editable(!read_only); + min_y_value->set_editable(!read_only); + label_x->set_editable(!read_only); + label_y->set_editable(!read_only); + edit_x->set_editable(!read_only); + edit_y->set_editable(!read_only); + tool_triangle->set_disabled(read_only); + auto_triangles->set_disabled(read_only); + sync->set_disabled(read_only); + interpolation->set_disabled(read_only); } StringName AnimationNodeBlendSpace2DEditor::get_blend_position_path() const { @@ -69,10 +94,17 @@ StringName AnimationNodeBlendSpace2DEditor::get_blend_position_path() const { } void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEvent> &p_event) { + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } + Ref<InputEventKey> k = p_event; if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && !k->is_echo()) { if (selected_point != -1 || selected_triangle != -1) { - _erase_selected(); + if (!read_only) { + _erase_selected(); + } accept_event(); } } @@ -80,62 +112,62 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) || (mb->get_button_index() == MouseButton::LEFT && tool_create->is_pressed()))) { - menu->clear(); - animations_menu->clear(); - animations_to_add.clear(); - List<StringName> classes; - classes.sort_custom<StringName::AlphCompare>(); - - ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); - menu->add_submenu_item(TTR("Add Animation"), "animations"); - - AnimationTree *gp = AnimationTreeEditor::get_singleton()->get_tree(); - ERR_FAIL_COND(!gp); - if (gp && gp->has_node(gp->get_animation_player())) { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player())); - if (ap) { - List<StringName> names; - ap->get_animation_list(&names); - for (const StringName &E : names) { - animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E); - animations_to_add.push_back(E); + if (!read_only) { + menu->clear(); + animations_menu->clear(); + animations_to_add.clear(); + List<StringName> classes; + classes.sort_custom<StringName::AlphCompare>(); + + ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); + menu->add_submenu_item(TTR("Add Animation"), "animations"); + + if (tree->has_node(tree->get_animation_player())) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(tree->get_node(tree->get_animation_player())); + if (ap) { + List<StringName> names; + ap->get_animation_list(&names); + for (const StringName &E : names) { + animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E); + animations_to_add.push_back(E); + } } } - } - for (const StringName &E : classes) { - String name = String(E).replace_first("AnimationNode", ""); - if (name == "Animation") { - continue; // nope + for (const StringName &E : classes) { + String name = String(E).replace_first("AnimationNode", ""); + if (name == "Animation" || name == "StartState" || name == "EndState") { + continue; // nope + } + int idx = menu->get_item_count(); + menu->add_item(vformat(TTR("Add %s"), name), idx); + menu->set_item_metadata(idx, E); } - int idx = menu->get_item_count(); - menu->add_item(vformat("Add %s", name), idx); - menu->set_item_metadata(idx, E); - } - Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard(); - if (clipb.is_valid()) { + Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard(); + if (clipb.is_valid()) { + menu->add_separator(); + menu->add_item(TTR("Paste"), MENU_PASTE); + } menu->add_separator(); - menu->add_item(TTR("Paste"), MENU_PASTE); - } - menu->add_separator(); - menu->add_item(TTR("Load..."), MENU_LOAD_FILE); - - menu->set_position(blend_space_draw->get_screen_position() + mb->get_position()); - menu->reset_size(); - menu->popup(); - add_point_pos = (mb->get_position() / blend_space_draw->get_size()); - add_point_pos.y = 1.0 - add_point_pos.y; - add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); - add_point_pos += blend_space->get_min_space(); - - if (snap->is_pressed()) { - add_point_pos = add_point_pos.snapped(blend_space->get_snap()); + menu->add_item(TTR("Load..."), MENU_LOAD_FILE); + + menu->set_position(blend_space_draw->get_screen_position() + mb->get_position()); + menu->reset_size(); + menu->popup(); + add_point_pos = (mb->get_position() / blend_space_draw->get_size()); + add_point_pos.y = 1.0 - add_point_pos.y; + add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); + add_point_pos += blend_space->get_min_space(); + + if (snap->is_pressed()) { + add_point_pos = add_point_pos.snapped(blend_space->get_snap()); + } } } if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { - blend_space_draw->update(); //update anyway + blend_space_draw->queue_redraw(); //update anyway //try to see if a point can be selected selected_point = -1; selected_triangle = -1; @@ -175,12 +207,12 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven } if (mb.is_valid() && mb->is_pressed() && tool_triangle->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { - blend_space_draw->update(); //update anyway + blend_space_draw->queue_redraw(); //update anyway //try to see if a point can be selected selected_point = -1; for (int i = 0; i < points.size(); i++) { - if (making_triangle.find(i) != -1) { + if (making_triangle.has(i)) { continue; } @@ -195,6 +227,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven } updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Triangle")); undo_redo->add_do_method(blend_space.ptr(), "add_triangle", making_triangle[0], making_triangle[1], making_triangle[2]); undo_redo->add_undo_method(blend_space.ptr(), "remove_triangle", blend_space->get_triangle_count()); @@ -218,21 +251,24 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven point = point.snapped(blend_space->get_snap()); } - updating = true; - undo_redo->create_action(TTR("Move Node Point")); - undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point); - undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); - undo_redo->add_do_method(this, "_update_space"); - undo_redo->add_undo_method(this, "_update_space"); - undo_redo->add_do_method(this, "_update_edited_point_pos"); - undo_redo->add_undo_method(this, "_update_edited_point_pos"); - undo_redo->commit_action(); - updating = false; - _update_edited_point_pos(); + if (!read_only) { + updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Move Node Point")); + undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point); + undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->add_do_method(this, "_update_edited_point_pos"); + undo_redo->add_undo_method(this, "_update_edited_point_pos"); + undo_redo->commit_action(); + updating = false; + _update_edited_point_pos(); + } } dragging_selected_attempt = false; dragging_selected = false; - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } if (mb.is_valid() && mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { @@ -241,43 +277,45 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); blend_pos += blend_space->get_min_space(); - AnimationTreeEditor::get_singleton()->get_tree()->set(get_blend_position_path(), blend_pos); + tree->set(get_blend_position_path(), blend_pos); - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid() && !blend_space_draw->has_focus()) { blend_space_draw->grab_focus(); - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } if (mm.is_valid() && dragging_selected_attempt) { dragging_selected = true; - drag_ofs = ((mm->get_position() - drag_from) / blend_space_draw->get_size()) * (blend_space->get_max_space() - blend_space->get_min_space()) * Vector2(1, -1); - blend_space_draw->update(); + if (!read_only) { + drag_ofs = ((mm->get_position() - drag_from) / blend_space_draw->get_size()) * (blend_space->get_max_space() - blend_space->get_min_space()) * Vector2(1, -1); + } + blend_space_draw->queue_redraw(); _update_edited_point_pos(); } if (mm.is_valid() && tool_triangle->is_pressed() && making_triangle.size()) { - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } if (mm.is_valid() && !tool_triangle->is_pressed() && making_triangle.size()) { making_triangle.clear(); - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } - if (mm.is_valid() && tool_blend->is_pressed() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { + if (mm.is_valid() && tool_blend->is_pressed() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { Vector2 blend_pos = (mm->get_position() / blend_space_draw->get_size()); blend_pos.y = 1.0 - blend_pos.y; blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); blend_pos += blend_space->get_min_space(); - AnimationTreeEditor::get_singleton()->get_tree()->set(get_blend_position_path(), blend_pos); + tree->set(get_blend_position_path(), blend_pos); - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } } @@ -285,6 +323,8 @@ void AnimationNodeBlendSpace2DEditor::_file_opened(const String &p_file) { file_loaded = ResourceLoader::load(p_file); if (file_loaded.is_valid()) { _add_menu_type(MENU_LOAD_FILE_CONFIRM); + } else { + EditorNode::get_singleton()->show_warning(TTR("This type of node can't be used. Only animation nodes are allowed.")); } } @@ -321,6 +361,7 @@ void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) { } updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Node Point")); undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", node, add_point_pos); undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count()); @@ -329,7 +370,7 @@ void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) { undo_redo->commit_action(); updating = false; - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace2DEditor::_add_animation_type(int p_index) { @@ -339,6 +380,7 @@ void AnimationNodeBlendSpace2DEditor::_add_animation_type(int p_index) { anim->set_animation(animations_to_add[p_index]); updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Animation Point")); undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", anim, add_point_pos); undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count()); @@ -347,11 +389,14 @@ void AnimationNodeBlendSpace2DEditor::_add_animation_type(int p_index) { undo_redo->commit_action(); updating = false; - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace2DEditor::_update_tool_erase() { - tool_erase->set_disabled(!(selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) && !(selected_triangle >= 0 && selected_triangle < blend_space->get_triangle_count())); + tool_erase->set_disabled( + (!(selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) && !(selected_triangle >= 0 && selected_triangle < blend_space->get_triangle_count())) || + read_only); + if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) { Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point); if (AnimationTreeEditor::get_singleton()->can_edit(an)) { @@ -359,7 +404,11 @@ void AnimationNodeBlendSpace2DEditor::_update_tool_erase() { } else { open_editor->hide(); } - edit_hb->show(); + if (!read_only) { + edit_hb->show(); + } else { + edit_hb->hide(); + } } else { edit_hb->hide(); } @@ -369,11 +418,11 @@ void AnimationNodeBlendSpace2DEditor::_tool_switch(int p_tool) { making_triangle.clear(); if (p_tool == 2) { - Vector<Vector2> points; + Vector<Vector2> bl_points; for (int i = 0; i < blend_space->get_blend_point_count(); i++) { - points.push_back(blend_space->get_blend_point_position(i)); + bl_points.push_back(blend_space->get_blend_point_position(i)); } - Vector<Delaunay2D::Triangle> tr = Delaunay2D::triangulate(points); + Vector<Delaunay2D::Triangle> tr = Delaunay2D::triangulate(bl_points); for (int i = 0; i < tr.size(); i++) { blend_space->add_triangle(tr[i].points[0], tr[i].points[1], tr[i].points[2]); } @@ -387,10 +436,15 @@ void AnimationNodeBlendSpace2DEditor::_tool_switch(int p_tool) { tool_erase_sep->hide(); } _update_tool_erase(); - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } + Color linecolor = get_theme_color(SNAME("font_color"), SNAME("Label")); Color linecolor_soft = linecolor; linecolor_soft.a *= 0.5; @@ -405,22 +459,22 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); blend_space_draw->draw_rect(Rect2(Point2(), s), color, false); } - blend_space_draw->draw_line(Point2(1, 0), Point2(1, s.height - 1), linecolor); - blend_space_draw->draw_line(Point2(1, s.height - 1), Point2(s.width - 1, s.height - 1), linecolor); + blend_space_draw->draw_line(Point2(1, 0), Point2(1, s.height - 1), linecolor, Math::round(EDSCALE)); + blend_space_draw->draw_line(Point2(1, s.height - 1), Point2(s.width - 1, s.height - 1), linecolor, Math::round(EDSCALE)); - blend_space_draw->draw_line(Point2(0, 0), Point2(5 * EDSCALE, 0), linecolor); + blend_space_draw->draw_line(Point2(0, 0), Point2(5 * EDSCALE, 0), linecolor, Math::round(EDSCALE)); if (blend_space->get_min_space().y < 0) { int y = (blend_space->get_max_space().y / (blend_space->get_max_space().y - blend_space->get_min_space().y)) * s.height; - blend_space_draw->draw_line(Point2(0, y), Point2(5 * EDSCALE, y), linecolor); + blend_space_draw->draw_line(Point2(0, y), Point2(5 * EDSCALE, y), linecolor, Math::round(EDSCALE)); blend_space_draw->draw_string(font, Point2(2 * EDSCALE, y - font->get_height(font_size) + font->get_ascent(font_size)), "0", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, linecolor); - blend_space_draw->draw_line(Point2(5 * EDSCALE, y), Point2(s.width, y), linecolor_soft); + blend_space_draw->draw_line(Point2(5 * EDSCALE, y), Point2(s.width, y), linecolor_soft, Math::round(EDSCALE)); } if (blend_space->get_min_space().x < 0) { int x = (-blend_space->get_min_space().x / (blend_space->get_max_space().x - blend_space->get_min_space().x)) * s.width; - blend_space_draw->draw_line(Point2(x, s.height - 1), Point2(x, s.height - 5 * EDSCALE), linecolor); + blend_space_draw->draw_line(Point2(x, s.height - 1), Point2(x, s.height - 5 * EDSCALE), linecolor, Math::round(EDSCALE)); blend_space_draw->draw_string(font, Point2(x + 2 * EDSCALE, s.height - 2 * EDSCALE - font->get_height(font_size) + font->get_ascent(font_size)), "0", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, linecolor); - blend_space_draw->draw_line(Point2(x, s.height - 5 * EDSCALE), Point2(x, 0), linecolor_soft); + blend_space_draw->draw_line(Point2(x, s.height - 5 * EDSCALE), Point2(x, 0), linecolor_soft, Math::round(EDSCALE)); } if (snap->is_pressed()) { @@ -433,7 +487,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { int idx = int(v / blend_space->get_snap().x); if (i > 0 && prev_idx != idx) { - blend_space_draw->draw_line(Point2(i, 0), Point2(i, s.height), linecolor_soft); + blend_space_draw->draw_line(Point2(i, 0), Point2(i, s.height), linecolor_soft, Math::round(EDSCALE)); } prev_idx = idx; @@ -447,7 +501,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { int idx = int(v / blend_space->get_snap().y); if (i > 0 && prev_idx != idx) { - blend_space_draw->draw_line(Point2(0, i), Point2(s.width, i), linecolor_soft); + blend_space_draw->draw_line(Point2(0, i), Point2(s.width, i), linecolor_soft, Math::round(EDSCALE)); } prev_idx = idx; @@ -457,8 +511,8 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { //triangles first for (int i = 0; i < blend_space->get_triangle_count(); i++) { - Vector<Vector2> points; - points.resize(3); + Vector<Vector2> bl_points; + bl_points.resize(3); for (int j = 0; j < 3; j++) { int point_idx = blend_space->get_triangle_point(i, j); @@ -472,11 +526,11 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); point *= s; point.y = s.height - point.y; - points.write[j] = point; + bl_points.write[j] = point; } for (int j = 0; j < 3; j++) { - blend_space_draw->draw_line(points[j], points[(j + 1) % 3], linecolor, 1); + blend_space_draw->draw_line(bl_points[j], bl_points[(j + 1) % 3], linecolor, Math::round(EDSCALE), true); } Color color; @@ -488,20 +542,23 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { color.a *= 0.2; } - Vector<Color> colors; - colors.push_back(color); - colors.push_back(color); - colors.push_back(color); - blend_space_draw->draw_primitive(points, colors, Vector<Vector2>()); + Vector<Color> colors = { + color, + color, + color + }; + blend_space_draw->draw_primitive(bl_points, colors, Vector<Vector2>()); } points.clear(); for (int i = 0; i < blend_space->get_blend_point_count(); i++) { Vector2 point = blend_space->get_blend_point_position(i); - if (dragging_selected && selected_point == i) { - point += drag_ofs; - if (snap->is_pressed()) { - point = point.snapped(blend_space->get_snap()); + if (!read_only) { + if (dragging_selected && selected_point == i) { + point += drag_ofs; + if (snap->is_pressed()) { + point = point.snapped(blend_space->get_snap()); + } } } point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); @@ -520,19 +577,19 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { } if (making_triangle.size()) { - Vector<Vector2> points; + Vector<Vector2> bl_points; for (int i = 0; i < making_triangle.size(); i++) { Vector2 point = blend_space->get_blend_point_position(making_triangle[i]); point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); point *= s; point.y = s.height - point.y; - points.push_back(point); + bl_points.push_back(point); } - for (int i = 0; i < points.size() - 1; i++) { - blend_space_draw->draw_line(points[i], points[i + 1], linecolor, 2); + for (int i = 0; i < bl_points.size() - 1; i++) { + blend_space_draw->draw_line(bl_points[i], bl_points[i + 1], linecolor, Math::round(2 * EDSCALE), true); } - blend_space_draw->draw_line(points[points.size() - 1], blend_space_draw->get_local_mouse_position(), linecolor, 2); + blend_space_draw->draw_line(bl_points[bl_points.size() - 1], blend_space_draw->get_local_mouse_position(), linecolor, Math::round(2 * EDSCALE), true); } ///draw cursor position @@ -546,7 +603,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { color.a *= 0.5; } - Vector2 blend_pos = AnimationTreeEditor::get_singleton()->get_tree()->get(get_blend_position_path()); + Vector2 blend_pos = tree->get(get_blend_position_path()); Vector2 point = blend_pos; point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); @@ -561,20 +618,20 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { Color lcol = color; lcol.a *= 0.4; - blend_space_draw->draw_line(point, closest, lcol, 2); + blend_space_draw->draw_line(point, closest, lcol, Math::round(2 * EDSCALE), true); } float mind = 5 * EDSCALE; float maxd = 15 * EDSCALE; - blend_space_draw->draw_line(point + Vector2(mind, 0), point + Vector2(maxd, 0), color, 2); - blend_space_draw->draw_line(point + Vector2(-mind, 0), point + Vector2(-maxd, 0), color, 2); - blend_space_draw->draw_line(point + Vector2(0, mind), point + Vector2(0, maxd), color, 2); - blend_space_draw->draw_line(point + Vector2(0, -mind), point + Vector2(0, -maxd), color, 2); + blend_space_draw->draw_line(point + Vector2(mind, 0), point + Vector2(maxd, 0), color, Math::round(2 * EDSCALE)); + blend_space_draw->draw_line(point + Vector2(-mind, 0), point + Vector2(-maxd, 0), color, Math::round(2 * EDSCALE)); + blend_space_draw->draw_line(point + Vector2(0, mind), point + Vector2(0, maxd), color, Math::round(2 * EDSCALE)); + blend_space_draw->draw_line(point + Vector2(0, -mind), point + Vector2(0, -maxd), color, Math::round(2 * EDSCALE)); } } void AnimationNodeBlendSpace2DEditor::_snap_toggled() { - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace2DEditor::_update_space() { @@ -592,6 +649,7 @@ void AnimationNodeBlendSpace2DEditor::_update_space() { auto_triangles->set_pressed(blend_space->get_auto_triangles()); + sync->set_pressed(blend_space->is_using_sync()); interpolation->select(blend_space->get_blend_mode()); max_x_value->set_value(blend_space->get_max_space().x); @@ -606,7 +664,7 @@ void AnimationNodeBlendSpace2DEditor::_update_space() { snap_x->set_value(blend_space->get_snap().x); snap_y->set_value(blend_space->get_snap().y); - blend_space_draw->update(); + blend_space_draw->queue_redraw(); updating = false; } @@ -617,13 +675,16 @@ void AnimationNodeBlendSpace2DEditor::_config_changed(double) { } updating = true; - undo_redo->create_action(TTR("Change BlendSpace2D Limits")); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Change BlendSpace2D Config")); undo_redo->add_do_method(blend_space.ptr(), "set_max_space", Vector2(max_x_value->get_value(), max_y_value->get_value())); undo_redo->add_undo_method(blend_space.ptr(), "set_max_space", blend_space->get_max_space()); undo_redo->add_do_method(blend_space.ptr(), "set_min_space", Vector2(min_x_value->get_value(), min_y_value->get_value())); undo_redo->add_undo_method(blend_space.ptr(), "set_min_space", blend_space->get_min_space()); undo_redo->add_do_method(blend_space.ptr(), "set_snap", Vector2(snap_x->get_value(), snap_y->get_value())); undo_redo->add_undo_method(blend_space.ptr(), "set_snap", blend_space->get_snap()); + undo_redo->add_do_method(blend_space.ptr(), "set_use_sync", sync->is_pressed()); + undo_redo->add_undo_method(blend_space.ptr(), "set_use_sync", blend_space->is_using_sync()); undo_redo->add_do_method(blend_space.ptr(), "set_blend_mode", interpolation->get_selected()); undo_redo->add_undo_method(blend_space.ptr(), "set_blend_mode", blend_space->get_blend_mode()); undo_redo->add_do_method(this, "_update_space"); @@ -631,7 +692,7 @@ void AnimationNodeBlendSpace2DEditor::_config_changed(double) { undo_redo->commit_action(); updating = false; - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace2DEditor::_labels_changed(String) { @@ -640,6 +701,7 @@ void AnimationNodeBlendSpace2DEditor::_labels_changed(String) { } updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Change BlendSpace2D Labels"), UndoRedo::MERGE_ENDS); undo_redo->add_do_method(blend_space.ptr(), "set_x_label", label_x->get_text()); undo_redo->add_undo_method(blend_space.ptr(), "set_x_label", blend_space->get_x_label()); @@ -652,6 +714,7 @@ void AnimationNodeBlendSpace2DEditor::_labels_changed(String) { } void AnimationNodeBlendSpace2DEditor::_erase_selected() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (selected_point != -1) { updating = true; undo_redo->create_action(TTR("Remove BlendSpace2D Point")); @@ -673,7 +736,7 @@ void AnimationNodeBlendSpace2DEditor::_erase_selected() { undo_redo->commit_action(); updating = false; - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } else if (selected_triangle != -1) { updating = true; undo_redo->create_action(TTR("Remove BlendSpace2D Triangle")); @@ -685,7 +748,7 @@ void AnimationNodeBlendSpace2DEditor::_erase_selected() { undo_redo->commit_action(); updating = false; - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } } @@ -714,6 +777,7 @@ void AnimationNodeBlendSpace2DEditor::_edit_point_pos(double) { return; } updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Move Node Point")); undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, Vector2(edit_x->get_value(), edit_y->get_value())); undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); @@ -724,53 +788,61 @@ void AnimationNodeBlendSpace2DEditor::_edit_point_pos(double) { undo_redo->commit_action(); updating = false; - blend_space_draw->update(); + blend_space_draw->queue_redraw(); } void AnimationNodeBlendSpace2DEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - tool_blend->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); - tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); - tool_create->set_icon(get_theme_icon(SNAME("EditKey"), SNAME("EditorIcons"))); - tool_triangle->set_icon(get_theme_icon(SNAME("ToolTriangle"), SNAME("EditorIcons"))); - tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - snap->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); - open_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); - auto_triangles->set_icon(get_theme_icon(SNAME("AutoTriangle"), SNAME("EditorIcons"))); - interpolation->clear(); - interpolation->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), "", 0); - interpolation->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), "", 1); - interpolation->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), "", 2); - } + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + tool_blend->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); + tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_create->set_icon(get_theme_icon(SNAME("EditKey"), SNAME("EditorIcons"))); + tool_triangle->set_icon(get_theme_icon(SNAME("ToolTriangle"), SNAME("EditorIcons"))); + tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + snap->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + open_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + auto_triangles->set_icon(get_theme_icon(SNAME("AutoTriangle"), SNAME("EditorIcons"))); + interpolation->clear(); + interpolation->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), "", 0); + interpolation->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), "", 1); + interpolation->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), "", 2); + } break; + + case NOTIFICATION_PROCESS: { + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } - if (p_what == NOTIFICATION_PROCESS) { - String error; - - if (!AnimationTreeEditor::get_singleton()->get_tree()) { - error = TTR("BlendSpace2D does not belong to an AnimationTree node."); - } else if (!AnimationTreeEditor::get_singleton()->get_tree()->is_active()) { - error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); - } else if (AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { - error = AnimationTreeEditor::get_singleton()->get_tree()->get_invalid_state_reason(); - } else if (blend_space->get_triangle_count() == 0) { - error = TTR("No triangles exist, so no blending can take place."); - } + String error; - if (error != error_label->get_text()) { - error_label->set_text(error); - if (!error.is_empty()) { - error_panel->show(); - } else { - error_panel->hide(); + if (!tree) { + error = TTR("BlendSpace2D does not belong to an AnimationTree node."); + } else if (!tree->is_active()) { + error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); + } else if (tree->is_state_invalid()) { + error = tree->get_invalid_state_reason(); + } else if (blend_space->get_triangle_count() == 0) { + error = TTR("No triangles exist, so no blending can take place."); } - } - } - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - set_process(is_visible_in_tree()); + if (error != error_label->get_text()) { + error_label->set_text(error); + if (!error.is_empty()) { + error_panel->show(); + } else { + error_panel->hide(); + } + } + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + set_process(is_visible_in_tree()); + } break; } } @@ -782,11 +854,8 @@ void AnimationNodeBlendSpace2DEditor::_open_editor() { } } -void AnimationNodeBlendSpace2DEditor::_removed_from_graph() { - EditorNode::get_singleton()->edit_item(nullptr); -} - void AnimationNodeBlendSpace2DEditor::_auto_triangles_toggled() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Toggle Auto Triangles")); undo_redo->add_do_method(blend_space.ptr(), "set_auto_triangles", auto_triangles->is_pressed()); undo_redo->add_undo_method(blend_space.ptr(), "set_auto_triangles", blend_space->get_auto_triangles()); @@ -800,8 +869,6 @@ void AnimationNodeBlendSpace2DEditor::_bind_methods() { ClassDB::bind_method("_update_tool_erase", &AnimationNodeBlendSpace2DEditor::_update_tool_erase); ClassDB::bind_method("_update_edited_point_pos", &AnimationNodeBlendSpace2DEditor::_update_edited_point_pos); - - ClassDB::bind_method("_removed_from_graph", &AnimationNodeBlendSpace2DEditor::_removed_from_graph); } AnimationNodeBlendSpace2DEditor *AnimationNodeBlendSpace2DEditor::singleton = nullptr; @@ -822,39 +889,39 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { tool_blend->set_button_group(bg); top_hb->add_child(tool_blend); tool_blend->set_pressed(true); - tool_blend->set_tooltip(TTR("Set the blending position within the space")); - tool_blend->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch), varray(3)); + tool_blend->set_tooltip_text(TTR("Set the blending position within the space")); + tool_blend->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch).bind(3)); tool_select = memnew(Button); tool_select->set_flat(true); tool_select->set_toggle_mode(true); tool_select->set_button_group(bg); top_hb->add_child(tool_select); - tool_select->set_tooltip(TTR("Select and move points, create points with RMB.")); - tool_select->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch), varray(0)); + tool_select->set_tooltip_text(TTR("Select and move points, create points with RMB.")); + tool_select->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch).bind(0)); tool_create = memnew(Button); tool_create->set_flat(true); tool_create->set_toggle_mode(true); tool_create->set_button_group(bg); top_hb->add_child(tool_create); - tool_create->set_tooltip(TTR("Create points.")); - tool_create->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch), varray(1)); + tool_create->set_tooltip_text(TTR("Create points.")); + tool_create->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch).bind(1)); tool_triangle = memnew(Button); tool_triangle->set_flat(true); tool_triangle->set_toggle_mode(true); tool_triangle->set_button_group(bg); top_hb->add_child(tool_triangle); - tool_triangle->set_tooltip(TTR("Create triangles by connecting points.")); - tool_triangle->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch), varray(2)); + tool_triangle->set_tooltip_text(TTR("Create triangles by connecting points.")); + tool_triangle->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch).bind(2)); tool_erase_sep = memnew(VSeparator); top_hb->add_child(tool_erase_sep); tool_erase = memnew(Button); tool_erase->set_flat(true); top_hb->add_child(tool_erase); - tool_erase->set_tooltip(TTR("Erase points and triangles.")); + tool_erase->set_tooltip_text(TTR("Erase points and triangles.")); tool_erase->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_erase_selected)); tool_erase->set_disabled(true); @@ -865,7 +932,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { top_hb->add_child(auto_triangles); auto_triangles->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_auto_triangles_toggled)); auto_triangles->set_toggle_mode(true); - auto_triangles->set_tooltip(TTR("Generate blend triangles automatically (instead of manually)")); + auto_triangles->set_tooltip_text(TTR("Generate blend triangles automatically (instead of manually)")); top_hb->add_child(memnew(VSeparator)); @@ -874,7 +941,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { snap->set_toggle_mode(true); top_hb->add_child(snap); snap->set_pressed(true); - snap->set_tooltip(TTR("Enable snap and show grid.")); + snap->set_tooltip_text(TTR("Enable snap and show grid.")); snap->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_snap_toggled)); snap_x = memnew(SpinBox); @@ -893,6 +960,13 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { top_hb->add_child(memnew(VSeparator)); + top_hb->add_child(memnew(Label(TTR("Sync:")))); + sync = memnew(CheckBox); + top_hb->add_child(sync); + sync->connect("toggled", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed)); + + top_hb->add_child(memnew(VSeparator)); + top_hb->add_child(memnew(Label(TTR("Blend:")))); interpolation = memnew(OptionButton); top_hb->add_child(interpolation); @@ -917,7 +991,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { open_editor = memnew(Button); edit_hb->add_child(open_editor); open_editor->set_text(TTR("Open Editor")); - open_editor->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_open_editor), varray(), CONNECT_DEFERRED); + open_editor->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_open_editor), CONNECT_DEFERRED); edit_hb->hide(); open_editor->hide(); @@ -1003,8 +1077,6 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { error_panel->add_child(error_label); error_label->set_text("eh"); - undo_redo = EditorNode::get_undo_redo(); - set_custom_minimum_size(Size2(0, 300 * EDSCALE)); menu = memnew(PopupMenu); @@ -1021,7 +1093,6 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { open_file->set_title(TTR("Open Animation Node")); open_file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); open_file->connect("file_selected", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_file_opened)); - undo_redo = EditorNode::get_undo_redo(); selected_point = -1; selected_triangle = -1; diff --git a/editor/plugins/animation_blend_space_2d_editor.h b/editor/plugins/animation_blend_space_2d_editor.h index b46efff304..f11cccabc1 100644 --- a/editor/plugins/animation_blend_space_2d_editor.h +++ b/editor/plugins/animation_blend_space_2d_editor.h @@ -1,89 +1,92 @@ -/*************************************************************************/ -/* animation_blend_space_2d_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_blend_space_2d_editor.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 ANIMATION_BLEND_SPACE_2D_EDITOR_H #define ANIMATION_BLEND_SPACE_2D_EDITOR_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/plugins/animation_tree_editor_plugin.h" -#include "editor/property_editor.h" #include "scene/animation/animation_blend_space_2d.h" #include "scene/gui/button.h" #include "scene/gui/graph_edit.h" #include "scene/gui/popup.h" +#include "scene/gui/separator.h" #include "scene/gui/tree.h" +class CheckBox; +class OptionButton; +class PanelContainer; + class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeBlendSpace2DEditor, AnimationTreeNodeEditorPlugin); Ref<AnimationNodeBlendSpace2D> blend_space; - - PanelContainer *panel; - Button *tool_blend; - Button *tool_select; - Button *tool_create; - Button *tool_triangle; - VSeparator *tool_erase_sep; - Button *tool_erase; - Button *snap; - SpinBox *snap_x; - SpinBox *snap_y; - OptionButton *interpolation; - - Button *auto_triangles; - - LineEdit *label_x; - LineEdit *label_y; - SpinBox *max_x_value; - SpinBox *min_x_value; - SpinBox *max_y_value; - SpinBox *min_y_value; - - HBoxContainer *edit_hb; - SpinBox *edit_x; - SpinBox *edit_y; - Button *open_editor; + bool read_only = false; + + PanelContainer *panel = nullptr; + Button *tool_blend = nullptr; + Button *tool_select = nullptr; + Button *tool_create = nullptr; + Button *tool_triangle = nullptr; + VSeparator *tool_erase_sep = nullptr; + Button *tool_erase = nullptr; + Button *snap = nullptr; + SpinBox *snap_x = nullptr; + SpinBox *snap_y = nullptr; + CheckBox *sync = nullptr; + OptionButton *interpolation = nullptr; + + Button *auto_triangles = nullptr; + + LineEdit *label_x = nullptr; + LineEdit *label_y = nullptr; + SpinBox *max_x_value = nullptr; + SpinBox *min_x_value = nullptr; + SpinBox *max_y_value = nullptr; + SpinBox *min_y_value = nullptr; + + HBoxContainer *edit_hb = nullptr; + SpinBox *edit_x = nullptr; + SpinBox *edit_y = nullptr; + Button *open_editor = nullptr; int selected_point; int selected_triangle; - Control *blend_space_draw; + Control *blend_space_draw = nullptr; - PanelContainer *error_panel; - Label *error_label; + PanelContainer *error_panel = nullptr; + Label *error_label = nullptr; bool updating; - UndoRedo *undo_redo; - static AnimationNodeBlendSpace2DEditor *singleton; void _blend_space_gui_input(const Ref<InputEvent> &p_event); @@ -95,8 +98,8 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { void _labels_changed(String); void _snap_toggled(); - PopupMenu *menu; - PopupMenu *animations_menu; + PopupMenu *menu = nullptr; + PopupMenu *animations_menu = nullptr; Vector<String> animations_to_add; Vector2 add_point_pos; Vector<Vector2> points; @@ -118,13 +121,11 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { void _edit_point_pos(double); void _open_editor(); - void _removed_from_graph(); - void _auto_triangles_toggled(); StringName get_blend_position_path() const; - EditorFileDialog *open_file; + EditorFileDialog *open_file = nullptr; Ref<AnimationNode> file_loaded; void _file_opened(const String &p_file); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 9ebdede4e9..77785b15ca 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* animation_blend_tree_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_blend_tree_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 "animation_blend_tree_editor_plugin.h" @@ -34,12 +34,19 @@ #include "core/input/input.h" #include "core/io/resource_loader.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" #include "editor/editor_inspector.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "scene/animation/animation_player.h" +#include "scene/gui/check_box.h" #include "scene/gui/menu_button.h" #include "scene/gui/panel.h" #include "scene/gui/progress_bar.h" +#include "scene/gui/separator.h" +#include "scene/gui/view_panner.h" #include "scene/main/window.h" void AnimationNodeBlendTreeEditor::add_custom_type(const String &p_name, const Ref<Script> &p_script) { @@ -91,19 +98,28 @@ Size2 AnimationNodeBlendTreeEditor::get_minimum_size() const { } void AnimationNodeBlendTreeEditor::_property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) { - AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_tree(); + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Parameter Changed:") + " " + String(p_property), UndoRedo::MERGE_ENDS); undo_redo->add_do_property(tree, p_property, p_value); undo_redo->add_undo_property(tree, p_property, tree->get(p_property)); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "update_graph"); + undo_redo->add_undo_method(this, "update_graph"); undo_redo->commit_action(); updating = false; } -void AnimationNodeBlendTreeEditor::_update_graph() { - if (updating) { +void AnimationNodeBlendTreeEditor::update_graph() { + if (updating || blend_tree.is_null()) { + return; + } + + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { return; } @@ -129,6 +145,8 @@ void AnimationNodeBlendTreeEditor::_update_graph() { GraphNode *node = memnew(GraphNode); graph->add_child(node); + node->set_draggable(!read_only); + Ref<AnimationNode> agnode = blend_tree->get_node(E); ERR_CONTINUE(!agnode.is_valid()); @@ -141,21 +159,23 @@ void AnimationNodeBlendTreeEditor::_update_graph() { if (String(E) != "output") { LineEdit *name = memnew(LineEdit); name->set_text(E); + name->set_editable(!read_only); name->set_expand_to_text_length_enabled(true); node->add_child(name); - node->set_slot(0, false, 0, Color(), true, 0, get_theme_color(SNAME("font_color"), SNAME("Label"))); - name->connect("text_submitted", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed), varray(agnode), CONNECT_DEFERRED); - name->connect("focus_exited", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed_focus_out), varray(name, agnode), CONNECT_DEFERRED); + node->set_slot(0, false, 0, Color(), true, read_only ? -1 : 0, get_theme_color(SNAME("font_color"), SNAME("Label"))); + name->connect("text_submitted", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed).bind(agnode), CONNECT_DEFERRED); + name->connect("focus_exited", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed_focus_out).bind(agnode), CONNECT_DEFERRED); + name->connect("text_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_rename_lineedit_changed), CONNECT_DEFERRED); base = 1; node->set_show_close_button(true); - node->connect("close_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_delete_request), varray(E), CONNECT_DEFERRED); + node->connect("close_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_delete_request).bind(E), CONNECT_DEFERRED); } for (int i = 0; i < agnode->get_input_count(); i++) { Label *in_name = memnew(Label); node->add_child(in_name); in_name->set_text(agnode->get_input_name(i)); - node->set_slot(base + i, true, 0, get_theme_color(SNAME("font_color"), SNAME("Label")), false, 0, Color()); + node->set_slot(base + i, true, read_only ? -1 : 0, get_theme_color(SNAME("font_color"), SNAME("Label")), false, 0, Color()); } List<PropertyInfo> pinfo; @@ -165,9 +185,10 @@ void AnimationNodeBlendTreeEditor::_update_graph() { continue; } String base_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E) + "/" + F.name; - EditorProperty *prop = EditorInspector::instantiate_property_editor(AnimationTreeEditor::get_singleton()->get_tree(), F.type, base_path, F.hint, F.hint_string, F.usage); + EditorProperty *prop = EditorInspector::instantiate_property_editor(tree, F.type, base_path, F.hint, F.hint_string, F.usage); if (prop) { - prop->set_object_and_property(AnimationTreeEditor::get_singleton()->get_tree(), base_path); + prop->set_read_only(read_only || (F.usage & PROPERTY_USAGE_READ_ONLY)); + prop->set_object_and_property(tree, base_path); prop->update_property(); prop->set_name_split_ratio(0); prop->connect("property_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_property_changed)); @@ -176,7 +197,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { } } - node->connect("dragged", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_dragged), varray(E)); + node->connect("dragged", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_dragged).bind(E)); if (AnimationTreeEditor::get_singleton()->can_edit(agnode)) { node->add_child(memnew(HSeparator)); @@ -184,18 +205,22 @@ void AnimationNodeBlendTreeEditor::_update_graph() { open_in_editor->set_text(TTR("Open Editor")); open_in_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); node->add_child(open_in_editor); - open_in_editor->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_open_in_editor), varray(E), CONNECT_DEFERRED); + open_in_editor->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_open_in_editor).bind(E), CONNECT_DEFERRED); open_in_editor->set_h_size_flags(SIZE_SHRINK_CENTER); } if (agnode->has_filter()) { node->add_child(memnew(HSeparator)); - Button *edit_filters = memnew(Button); - edit_filters->set_text(TTR("Edit Filters")); - edit_filters->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons"))); - node->add_child(edit_filters); - edit_filters->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_edit_filters), varray(E), CONNECT_DEFERRED); - edit_filters->set_h_size_flags(SIZE_SHRINK_CENTER); + Button *inspect_filters = memnew(Button); + if (read_only) { + inspect_filters->set_text(TTR("Inspect Filters")); + } else { + inspect_filters->set_text(TTR("Edit Filters")); + } + inspect_filters->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons"))); + node->add_child(inspect_filters); + inspect_filters->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_inspect_filters).bind(E), CONNECT_DEFERRED); + inspect_filters->set_h_size_flags(SIZE_SHRINK_CENTER); } Ref<AnimationNodeAnimation> anim = agnode; @@ -203,6 +228,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { MenuButton *mb = memnew(MenuButton); mb->set_text(anim->get_animation()); mb->set_icon(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons"))); + mb->set_disabled(read_only); Array options; node->add_child(memnew(HSeparator)); @@ -210,9 +236,8 @@ void AnimationNodeBlendTreeEditor::_update_graph() { ProgressBar *pb = memnew(ProgressBar); - AnimationTree *player = AnimationTreeEditor::get_singleton()->get_tree(); - if (player->has_node(player->get_animation_player())) { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(player->get_node(player->get_animation_player())); + if (tree->has_node(tree->get_animation_player())) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(tree->get_node(tree->get_animation_player())); if (ap) { List<StringName> anims; ap->get_animation_list(&anims); @@ -228,12 +253,12 @@ void AnimationNodeBlendTreeEditor::_update_graph() { } } - pb->set_percent_visible(false); + pb->set_show_percentage(false); pb->set_custom_minimum_size(Vector2(0, 14) * EDSCALE); animations[E] = pb; node->add_child(pb); - mb->get_popup()->connect("index_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_anim_selected), varray(options, E), CONNECT_DEFERRED); + mb->get_popup()->connect("index_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_anim_selected).bind(options, E), CONNECT_DEFERRED); } Ref<StyleBoxFlat> sb = node->get_theme_stylebox(SNAME("frame"), SNAME("GraphNode")); @@ -248,10 +273,10 @@ void AnimationNodeBlendTreeEditor::_update_graph() { node->add_theme_color_override("resizer_color", c); } - List<AnimationNodeBlendTree::NodeConnection> connections; - blend_tree->get_node_connections(&connections); + List<AnimationNodeBlendTree::NodeConnection> node_connections; + blend_tree->get_node_connections(&node_connections); - for (const AnimationNodeBlendTree::NodeConnection &E : connections) { + for (const AnimationNodeBlendTree::NodeConnection &E : node_connections) { StringName from = E.output_node; StringName to = E.input_node; int to_idx = E.input_index; @@ -259,14 +284,18 @@ void AnimationNodeBlendTreeEditor::_update_graph() { graph->connect_node(from, 0, to, to_idx); } - float graph_minimap_opacity = EditorSettings::get_singleton()->get("editors/visual_editors/minimap_opacity"); + float graph_minimap_opacity = EDITOR_GET("editors/visual_editors/minimap_opacity"); graph->set_minimap_opacity(graph_minimap_opacity); + float graph_lines_curvature = EDITOR_GET("editors/visual_editors/lines_curvature"); + graph->set_connection_lines_curvature(graph_lines_curvature); } void AnimationNodeBlendTreeEditor::_file_opened(const String &p_file) { file_loaded = ResourceLoader::load(p_file); if (file_loaded.is_valid()) { _add_node(MENU_LOAD_FILE_CONFIRM); + } else { + EditorNode::get_singleton()->show_warning(TTR("This type of node can't be used. Only animation nodes are allowed.")); } } @@ -277,9 +306,9 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { if (p_idx == MENU_LOAD_FILE) { open_file->clear_filters(); - List<String> filters; - ResourceLoader::get_recognized_extensions_for_type("AnimationNode", &filters); - for (const String &E : filters) { + List<String> ext_filters; + ResourceLoader::get_recognized_extensions_for_type("AnimationNode", &ext_filters); + for (const String &E : ext_filters) { open_file->add_filter("*." + E); } open_file->popup_file_dialog(); @@ -299,7 +328,7 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { base_name = add_options[p_idx].name; } else { ERR_FAIL_COND(add_options[p_idx].script.is_null()); - String base_type = add_options[p_idx].script->get_instance_base_type(); + StringName base_type = add_options[p_idx].script->get_instance_base_type(); AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instantiate(base_type)); ERR_FAIL_COND(!an); anode = Ref<AnimationNode>(an); @@ -334,6 +363,7 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { name = base_name + " " + itos(base); } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Node to BlendTree")); undo_redo->add_do_method(blend_tree.ptr(), "add_node", name, anode, instance_pos / EDSCALE); undo_redo->add_undo_method(blend_tree.ptr(), "remove_node", name); @@ -348,53 +378,75 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { to_slot = -1; } - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "update_graph"); + undo_redo->add_undo_method(this, "update_graph"); undo_redo->commit_action(); } -void AnimationNodeBlendTreeEditor::_popup(bool p_has_input_ports, const Vector2 &p_popup_position, const Vector2 &p_node_position) { +void AnimationNodeBlendTreeEditor::_popup(bool p_has_input_ports, const Vector2 &p_node_position) { _update_options_menu(p_has_input_ports); use_position_from_popup_menu = true; position_from_popup_menu = p_node_position; - add_node->get_popup()->set_position(p_popup_position); + add_node->get_popup()->set_position(graph->get_screen_position() + graph->get_local_mouse_position()); add_node->get_popup()->reset_size(); add_node->get_popup()->popup(); } void AnimationNodeBlendTreeEditor::_popup_request(const Vector2 &p_position) { - _popup(false, graph->get_screen_position() + graph->get_local_mouse_position(), p_position); + if (read_only) { + return; + } + + _popup(false, p_position); } void AnimationNodeBlendTreeEditor::_connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position) { + if (read_only) { + return; + } + Ref<AnimationNode> node = blend_tree->get_node(p_from); if (node.is_valid()) { from_node = p_from; - _popup(true, p_release_position, graph->get_global_mouse_position()); + _popup(true, p_release_position); } } void AnimationNodeBlendTreeEditor::_connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position) { + if (read_only) { + return; + } + Ref<AnimationNode> node = blend_tree->get_node(p_to); if (node.is_valid()) { to_node = p_to; to_slot = p_to_slot; - _popup(false, p_release_position, graph->get_global_mouse_position()); + _popup(false, p_release_position); } } +void AnimationNodeBlendTreeEditor::_popup_hide() { + to_node = ""; + to_slot = -1; +} + void AnimationNodeBlendTreeEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, const StringName &p_which) { updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Node Moved")); undo_redo->add_do_method(blend_tree.ptr(), "set_node_position", p_which, p_to / EDSCALE); undo_redo->add_undo_method(blend_tree.ptr(), "set_node_position", p_which, p_from / EDSCALE); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "update_graph"); + undo_redo->add_undo_method(this, "update_graph"); undo_redo->commit_action(); updating = false; } void AnimationNodeBlendTreeEditor::_connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) { + if (read_only) { + return; + } + AnimationNodeBlendTree::ConnectionError err = blend_tree->can_connect_node(p_to, p_to_index, p_from); if (err != AnimationNodeBlendTree::CONNECTION_OK) { @@ -402,23 +454,29 @@ void AnimationNodeBlendTreeEditor::_connection_request(const String &p_from, int return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Nodes Connected")); undo_redo->add_do_method(blend_tree.ptr(), "connect_node", p_to, p_to_index, p_from); undo_redo->add_undo_method(blend_tree.ptr(), "disconnect_node", p_to, p_to_index); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "update_graph"); + undo_redo->add_undo_method(this, "update_graph"); undo_redo->commit_action(); } void AnimationNodeBlendTreeEditor::_disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) { + if (read_only) { + return; + } + graph->disconnect_node(p_from, p_from_index, p_to, p_to_index); updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Nodes Disconnected")); undo_redo->add_do_method(blend_tree.ptr(), "disconnect_node", p_to, p_to_index); undo_redo->add_undo_method(blend_tree.ptr(), "connect_node", p_to, p_to_index, p_from); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "update_graph"); + undo_redo->add_undo_method(this, "update_graph"); undo_redo->commit_action(); updating = false; } @@ -429,15 +487,21 @@ void AnimationNodeBlendTreeEditor::_anim_selected(int p_index, Array p_options, Ref<AnimationNodeAnimation> anim = blend_tree->get_node(p_node); ERR_FAIL_COND(!anim.is_valid()); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Set Animation")); undo_redo->add_do_method(anim.ptr(), "set_animation", option); undo_redo->add_undo_method(anim.ptr(), "set_animation", anim->get_animation()); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "update_graph"); + undo_redo->add_undo_method(this, "update_graph"); undo_redo->commit_action(); } void AnimationNodeBlendTreeEditor::_delete_request(const String &p_which) { + if (read_only) { + return; + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Delete Node")); undo_redo->add_do_method(blend_tree.ptr(), "remove_node", p_which); undo_redo->add_undo_method(blend_tree.ptr(), "add_node", p_which, blend_tree->get_node(p_which), blend_tree.ptr()->get_node_position(p_which)); @@ -451,27 +515,38 @@ void AnimationNodeBlendTreeEditor::_delete_request(const String &p_which) { } } - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "update_graph"); + undo_redo->add_undo_method(this, "update_graph"); undo_redo->commit_action(); } -void AnimationNodeBlendTreeEditor::_delete_nodes_request() { +void AnimationNodeBlendTreeEditor::_delete_nodes_request(const TypedArray<StringName> &p_nodes) { + if (read_only) { + return; + } + List<StringName> to_erase; - for (int i = 0; i < graph->get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); - if (gn) { - if (gn->is_selected() && gn->is_close_button_visible()) { - to_erase.push_back(gn->get_name()); + if (p_nodes.is_empty()) { + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); + if (gn) { + if (gn->is_selected() && gn->is_close_button_visible()) { + to_erase.push_back(gn->get_name()); + } } } + } else { + for (int i = 0; i < p_nodes.size(); i++) { + to_erase.push_back(p_nodes[i]); + } } if (to_erase.is_empty()) { return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Delete Node(s)")); for (const StringName &F : to_erase) { @@ -482,6 +557,10 @@ void AnimationNodeBlendTreeEditor::_delete_nodes_request() { } void AnimationNodeBlendTreeEditor::_node_selected(Object *p_node) { + if (read_only) { + return; + } + GraphNode *gn = Object::cast_to<GraphNode>(p_node); ERR_FAIL_COND(!gn); @@ -501,6 +580,7 @@ void AnimationNodeBlendTreeEditor::_open_in_editor(const String &p_which) { void AnimationNodeBlendTreeEditor::_filter_toggled() { updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Toggle Filter On/Off")); undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_enabled", filter_enabled->is_pressed()); undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_enabled", _filter_edit->is_filter_enabled()); @@ -518,6 +598,7 @@ void AnimationNodeBlendTreeEditor::_filter_edited() { bool filtered = edited->is_checked(0); updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Change Filter")); undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_path", edited_path, filtered); undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_path", edited_path, _filter_edit->is_path_filtered(edited_path)); @@ -532,14 +613,19 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano return false; } - NodePath player_path = AnimationTreeEditor::get_singleton()->get_tree()->get_animation_player(); + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return false; + } + + NodePath player_path = tree->get_animation_player(); - if (!AnimationTreeEditor::get_singleton()->get_tree()->has_node(player_path)) { + if (!tree->has_node(player_path)) { EditorNode::get_singleton()->show_warning(TTR("No animation player set, so unable to retrieve track names.")); return false; } - AnimationPlayer *player = Object::cast_to<AnimationPlayer>(AnimationTreeEditor::get_singleton()->get_tree()->get_node(player_path)); + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(tree->get_node(player_path)); if (!player) { EditorNode::get_singleton()->show_warning(TTR("Player path set is invalid, so unable to retrieve track names.")); return false; @@ -554,13 +640,13 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano updating = true; - Set<String> paths; - HashMap<String, Set<String>> types; + HashSet<String> paths; + HashMap<String, RBSet<String>> types; { - List<StringName> animations; - player->get_animation_list(&animations); + List<StringName> animation_list; + player->get_animation_list(&animation_list); - for (const StringName &E : animations) { + for (const StringName &E : animation_list) { Ref<Animation> anim = player->get_animation(E); for (int i = 0; i < anim->get_track_count(); i++) { String track_path = anim->track_get_path(i); @@ -592,10 +678,10 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano filters->clear(); TreeItem *root = filters->create_item(); - Map<String, TreeItem *> parenthood; + HashMap<String, TreeItem *> parenthood; - for (Set<String>::Element *E = paths.front(); E; E = E->next()) { - NodePath path = E->get(); + for (const String &E : paths) { + NodePath path = E; TreeItem *ti = nullptr; String accum; for (int i = 0; i < path.get_name_count(); i++) { @@ -666,7 +752,7 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano } } - ti->set_editable(0, true); + ti->set_editable(0, !read_only); ti->set_selectable(0, true); ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); ti->set_text(0, concat); @@ -679,7 +765,7 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano ti = filters->create_item(ti); ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); ti->set_text(0, concat); - ti->set_editable(0, true); + ti->set_editable(0, !read_only); ti->set_selectable(0, true); ti->set_checked(0, anode->is_path_filtered(path)); ti->set_metadata(0, path); @@ -689,18 +775,19 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano //just a node, not a property track String types_text = "["; if (types.has(path)) { - Set<String>::Element *F = types[path].front(); - types_text += F->get(); - while (F->next()) { - F = F->next(); - types_text += " / " + F->get(); + RBSet<String>::Iterator F = types[path].begin(); + types_text += *F; + while (F) { + types_text += " / " + *F; + ; + ++F; } } types_text += "]"; ti = filters->create_item(ti); ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); ti->set_text(0, types_text); - ti->set_editable(0, true); + ti->set_editable(0, !read_only); ti->set_selectable(0, true); ti->set_checked(0, anode->is_path_filtered(path)); ti->set_metadata(0, path); @@ -713,7 +800,15 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano return true; } -void AnimationNodeBlendTreeEditor::_edit_filters(const String &p_which) { +void AnimationNodeBlendTreeEditor::_inspect_filters(const String &p_which) { + if (read_only) { + filter_dialog->set_title(TTR("Inspect Filtered Tracks:")); + } else { + filter_dialog->set_title(TTR("Edit Filtered Tracks:")); + } + + filter_enabled->set_disabled(read_only); + Ref<AnimationNode> anode = blend_tree->get_node(p_which); ERR_FAIL_COND(!anode.is_valid()); @@ -725,85 +820,107 @@ void AnimationNodeBlendTreeEditor::_edit_filters(const String &p_which) { filter_dialog->popup_centered(Size2(500, 500) * EDSCALE); } -void AnimationNodeBlendTreeEditor::_removed_from_graph() { - if (is_visible()) { - EditorNode::get_singleton()->edit_item(nullptr); - } +void AnimationNodeBlendTreeEditor::_update_editor_settings() { + graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); + graph->set_warped_panning(bool(EDITOR_GET("editors/panning/warped_mouse_panning"))); +} + +void AnimationNodeBlendTreeEditor::_update_theme() { + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); } void AnimationNodeBlendTreeEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + _update_editor_settings(); + _update_theme(); + } break; - if (p_what == NOTIFICATION_THEME_CHANGED && is_visible_in_tree()) { - _update_graph(); - } - } + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + _update_editor_settings(); + } break; - if (p_what == NOTIFICATION_PROCESS) { - String error; + case NOTIFICATION_THEME_CHANGED: { + _update_theme(); - if (!AnimationTreeEditor::get_singleton()->get_tree()->is_active()) { - error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); - } else if (AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { - error = AnimationTreeEditor::get_singleton()->get_tree()->get_invalid_state_reason(); - } + if (is_visible_in_tree()) { + update_graph(); + } + } break; - if (error != error_label->get_text()) { - error_label->set_text(error); - if (!error.is_empty()) { - error_panel->show(); - } else { - error_panel->hide(); + case NOTIFICATION_PROCESS: { + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; // Node has been changed. } - } - List<AnimationNodeBlendTree::NodeConnection> conns; - blend_tree->get_node_connections(&conns); - for (const AnimationNodeBlendTree::NodeConnection &E : conns) { - float activity = 0; - StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; - if (AnimationTreeEditor::get_singleton()->get_tree() && !AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { - activity = AnimationTreeEditor::get_singleton()->get_tree()->get_connection_activity(path, E.input_index); + String error; + + if (!tree->is_active()) { + error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); + } else if (tree->is_state_invalid()) { + error = tree->get_invalid_state_reason(); } - graph->set_connection_activity(E.output_node, 0, E.input_node, E.input_index, activity); - } - AnimationTree *graph_player = AnimationTreeEditor::get_singleton()->get_tree(); - AnimationPlayer *player = nullptr; - if (graph_player->has_node(graph_player->get_animation_player())) { - player = Object::cast_to<AnimationPlayer>(graph_player->get_node(graph_player->get_animation_player())); - } + if (error != error_label->get_text()) { + error_label->set_text(error); + if (!error.is_empty()) { + error_panel->show(); + } else { + error_panel->hide(); + } + } + + List<AnimationNodeBlendTree::NodeConnection> conns; + blend_tree->get_node_connections(&conns); + for (const AnimationNodeBlendTree::NodeConnection &E : conns) { + float activity = 0; + StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; + if (!tree->is_state_invalid()) { + activity = tree->get_connection_activity(path, E.input_index); + } + graph->set_connection_activity(E.output_node, 0, E.input_node, E.input_index, activity); + } + + AnimationPlayer *player = nullptr; + if (tree->has_node(tree->get_animation_player())) { + player = Object::cast_to<AnimationPlayer>(tree->get_node(tree->get_animation_player())); + } - if (player) { - for (const KeyValue<StringName, ProgressBar *> &E : animations) { - Ref<AnimationNodeAnimation> an = blend_tree->get_node(E.key); - if (an.is_valid()) { - if (player->has_animation(an->get_animation())) { - Ref<Animation> anim = player->get_animation(an->get_animation()); - if (anim.is_valid()) { - E.value->set_max(anim->get_length()); - //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; - StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time"; - E.value->set_value(AnimationTreeEditor::get_singleton()->get_tree()->get(time_path)); + if (player) { + for (const KeyValue<StringName, ProgressBar *> &E : animations) { + Ref<AnimationNodeAnimation> an = blend_tree->get_node(E.key); + if (an.is_valid()) { + if (player->has_animation(an->get_animation())) { + Ref<Animation> anim = player->get_animation(an->get_animation()); + if (anim.is_valid()) { + E.value->set_max(anim->get_length()); + //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; + StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time"; + E.value->set_value(tree->get(time_path)); + } } } } } - } - for (int i = 0; i < visible_properties.size(); i++) { - visible_properties[i]->update_property(); - } - } + for (int i = 0; i < visible_properties.size(); i++) { + visible_properties[i]->update_property(); + } + } break; - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - set_process(is_visible_in_tree()); + case NOTIFICATION_VISIBILITY_CHANGED: { + set_process(is_visible_in_tree()); + } break; } } void AnimationNodeBlendTreeEditor::_scroll_changed(const Vector2 &p_scroll) { + if (read_only) { + return; + } + if (updating) { return; } @@ -813,13 +930,33 @@ void AnimationNodeBlendTreeEditor::_scroll_changed(const Vector2 &p_scroll) { } void AnimationNodeBlendTreeEditor::_bind_methods() { - ClassDB::bind_method("_update_graph", &AnimationNodeBlendTreeEditor::_update_graph); + ClassDB::bind_method("update_graph", &AnimationNodeBlendTreeEditor::update_graph); ClassDB::bind_method("_update_filters", &AnimationNodeBlendTreeEditor::_update_filters); } AnimationNodeBlendTreeEditor *AnimationNodeBlendTreeEditor::singleton = nullptr; +// AnimationNode's "node_changed" signal means almost update_input. +void AnimationNodeBlendTreeEditor::_node_changed(const StringName &p_node_name) { + // TODO: + // Here is executed during the commit of EditorNode::undo_redo, it is not possible to create an undo_redo action here. + // The disconnect when the number of enabled inputs decreases is done in AnimationNodeBlendTree and update_graph(). + // This means that there is no place to register undo_redo actions. + // In order to implement undo_redo correctly, we may need to implement AnimationNodeEdit such as AnimationTrackKeyEdit + // and add it to _node_selected() with EditorNode::get_singleton()->push_item(AnimationNodeEdit). + update_graph(); +} + void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<AnimationNode> p_node) { + if (blend_tree.is_null()) { + return; + } + + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } + String prev_name = blend_tree->get_node_name(p_node); ERR_FAIL_COND(prev_name.is_empty()); GraphNode *gn = Object::cast_to<GraphNode>(graph->get_node(prev_name)); @@ -827,7 +964,7 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<Anima const String &new_name = p_text; - ERR_FAIL_COND(new_name.is_empty() || new_name.find(".") != -1 || new_name.find("/") != -1); + ERR_FAIL_COND(new_name.is_empty() || new_name.contains(".") || new_name.contains("/")); if (new_name == prev_name) { return; //nothing to do @@ -844,13 +981,12 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<Anima String base_path = AnimationTreeEditor::get_singleton()->get_base_path(); updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Node Renamed")); undo_redo->add_do_method(blend_tree.ptr(), "rename_node", prev_name, name); undo_redo->add_undo_method(blend_tree.ptr(), "rename_node", name, prev_name); - undo_redo->add_do_method(AnimationTreeEditor::get_singleton()->get_tree(), "rename_parameter", base_path + prev_name, base_path + name); - undo_redo->add_undo_method(AnimationTreeEditor::get_singleton()->get_tree(), "rename_parameter", base_path + name, base_path + prev_name); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "update_graph"); + undo_redo->add_undo_method(this, "update_graph"); undo_redo->commit_action(); updating = false; gn->set_name(new_name); @@ -868,10 +1004,10 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<Anima //recreate connections graph->clear_connections(); - List<AnimationNodeBlendTree::NodeConnection> connections; - blend_tree->get_node_connections(&connections); + List<AnimationNodeBlendTree::NodeConnection> node_connections; + blend_tree->get_node_connections(&node_connections); - for (const AnimationNodeBlendTree::NodeConnection &E : connections) { + for (const AnimationNodeBlendTree::NodeConnection &E : node_connections) { StringName from = E.output_node; StringName to = E.input_node; int to_idx = E.input_index; @@ -880,19 +1016,27 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<Anima } //update animations - for (Map<StringName, ProgressBar *>::Element *E = animations.front(); E; E = E->next()) { - if (E->key() == prev_name) { + for (const KeyValue<StringName, ProgressBar *> &E : animations) { + if (E.key == prev_name) { animations[new_name] = animations[prev_name]; animations.erase(prev_name); break; } } - _update_graph(); // Needed to update the signal connections with the new name. + update_graph(); // Needed to update the signal connections with the new name. + current_node_rename_text = String(); } -void AnimationNodeBlendTreeEditor::_node_renamed_focus_out(Node *le, Ref<AnimationNode> p_node) { - _node_renamed(le->call("get_text"), p_node); +void AnimationNodeBlendTreeEditor::_node_renamed_focus_out(Ref<AnimationNode> p_node) { + if (current_node_rename_text.is_empty()) { + return; // The text_submitted signal triggered the graph update and freed the LineEdit. + } + _node_renamed(current_node_rename_text, p_node); +} + +void AnimationNodeBlendTreeEditor::_node_rename_lineedit_changed(const String &p_text) { + current_node_rename_text = p_text; } bool AnimationNodeBlendTreeEditor::can_edit(const Ref<AnimationNode> &p_node) { @@ -902,18 +1046,25 @@ bool AnimationNodeBlendTreeEditor::can_edit(const Ref<AnimationNode> &p_node) { void AnimationNodeBlendTreeEditor::edit(const Ref<AnimationNode> &p_node) { if (blend_tree.is_valid()) { - blend_tree->disconnect("removed_from_graph", callable_mp(this, &AnimationNodeBlendTreeEditor::_removed_from_graph)); + blend_tree->disconnect("node_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_changed)); } blend_tree = p_node; + read_only = false; + if (blend_tree.is_null()) { hide(); } else { - blend_tree->connect("removed_from_graph", callable_mp(this, &AnimationNodeBlendTreeEditor::_removed_from_graph)); + read_only = EditorNode::get_singleton()->is_resource_read_only(blend_tree); - _update_graph(); + blend_tree->connect("node_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_changed)); + + update_graph(); } + + add_node->set_disabled(read_only); + graph->set_arrange_nodes_button_hidden(read_only); } AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { @@ -926,16 +1077,18 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { graph->add_valid_right_disconnect_type(0); graph->add_valid_left_disconnect_type(0); graph->set_v_size_flags(SIZE_EXPAND_FILL); - graph->connect("connection_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_request), varray(), CONNECT_DEFERRED); - graph->connect("disconnection_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_disconnection_request), varray(), CONNECT_DEFERRED); + graph->connect("connection_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_request), CONNECT_DEFERRED); + graph->connect("disconnection_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_disconnection_request), CONNECT_DEFERRED); graph->connect("node_selected", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_selected)); graph->connect("scroll_offset_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_scroll_changed)); graph->connect("delete_nodes_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_delete_nodes_request)); graph->connect("popup_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_popup_request)); graph->connect("connection_to_empty", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_to_empty)); graph->connect("connection_from_empty", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_from_empty)); - float graph_minimap_opacity = EditorSettings::get_singleton()->get("editors/visual_editors/minimap_opacity"); + float graph_minimap_opacity = EDITOR_GET("editors/visual_editors/minimap_opacity"); graph->set_minimap_opacity(graph_minimap_opacity); + float graph_lines_curvature = EDITOR_GET("editors/visual_editors/lines_curvature"); + graph->set_connection_lines_curvature(graph_lines_curvature); VSeparator *vs = memnew(VSeparator); graph->get_zoom_hbox()->add_child(vs); @@ -946,7 +1099,9 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { add_node->set_text(TTR("Add Node...")); graph->get_zoom_hbox()->move_child(add_node, 0); add_node->get_popup()->connect("id_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_add_node)); - add_node->connect("about_to_popup", callable_mp(this, &AnimationNodeBlendTreeEditor::_update_options_menu), varray(false)); + add_node->get_popup()->connect("popup_hide", callable_mp(this, &AnimationNodeBlendTreeEditor::_popup_hide), CONNECT_DEFERRED); + add_node->connect("about_to_popup", callable_mp(this, &AnimationNodeBlendTreeEditor::_update_options_menu).bind(false)); + add_node->set_disabled(read_only); add_options.push_back(AddOption("Animation", "AnimationNodeAnimation")); add_options.push_back(AddOption("OneShot", "AnimationNodeOneShot", 2)); @@ -954,7 +1109,7 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { add_options.push_back(AddOption("Add3", "AnimationNodeAdd3", 3)); add_options.push_back(AddOption("Blend2", "AnimationNodeBlend2", 2)); add_options.push_back(AddOption("Blend3", "AnimationNodeBlend3", 3)); - add_options.push_back(AddOption("Seek", "AnimationNodeTimeSeek", 1)); + add_options.push_back(AddOption("TimeSeek", "AnimationNodeTimeSeek", 1)); add_options.push_back(AddOption("TimeScale", "AnimationNodeTimeScale", 1)); add_options.push_back(AddOption("Transition", "AnimationNodeTransition")); add_options.push_back(AddOption("BlendTree", "AnimationNodeBlendTree")); @@ -992,5 +1147,4 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { open_file->set_title(TTR("Open Animation Node")); open_file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); open_file->connect("file_selected", callable_mp(this, &AnimationNodeBlendTreeEditor::_file_opened)); - undo_redo = EditorNode::get_undo_redo(); } diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h index 8e63e39fd5..afb3394238 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.h +++ b/editor/plugins/animation_blend_tree_editor_plugin.h @@ -1,81 +1,84 @@ -/*************************************************************************/ -/* animation_blend_tree_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_blend_tree_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 ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H #define ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H -#include "editor/editor_node.h" -#include "editor/editor_plugin.h" #include "editor/plugins/animation_tree_editor_plugin.h" -#include "editor/property_editor.h" #include "scene/animation/animation_blend_tree.h" #include "scene/gui/button.h" #include "scene/gui/graph_edit.h" +#include "scene/gui/panel_container.h" #include "scene/gui/popup.h" #include "scene/gui/tree.h" +class AcceptDialog; +class CheckBox; class ProgressBar; +class EditorFileDialog; +class EditorProperty; +class MenuButton; +class PanelContainer; class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeBlendTreeEditor, AnimationTreeNodeEditorPlugin); Ref<AnimationNodeBlendTree> blend_tree; - GraphEdit *graph; - MenuButton *add_node; + + bool read_only = false; + + GraphEdit *graph = nullptr; + MenuButton *add_node = nullptr; Vector2 position_from_popup_menu; bool use_position_from_popup_menu; - PanelContainer *error_panel; - Label *error_label; - - UndoRedo *undo_redo; + PanelContainer *error_panel = nullptr; + Label *error_label = nullptr; - AcceptDialog *filter_dialog; - Tree *filters; - CheckBox *filter_enabled; + AcceptDialog *filter_dialog = nullptr; + Tree *filters = nullptr; + CheckBox *filter_enabled = nullptr; - Map<StringName, ProgressBar *> animations; + HashMap<StringName, ProgressBar *> animations; Vector<EditorProperty *> visible_properties; String to_node = ""; int to_slot = -1; String from_node = ""; - void _update_graph(); - struct AddOption { String name; String type; Ref<Script> script; int input_port_count; - AddOption(const String &p_name = String(), const String &p_type = String(), bool p_input_port_count = 0) : + AddOption(const String &p_name = String(), const String &p_type = String(), int p_input_port_count = 0) : name(p_name), type(p_type), input_port_count(p_input_port_count) { @@ -91,8 +94,11 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { void _node_dragged(const Vector2 &p_from, const Vector2 &p_to, const StringName &p_which); void _node_renamed(const String &p_text, Ref<AnimationNode> p_node); - void _node_renamed_focus_out(Node *le, Ref<AnimationNode> p_node); + void _node_renamed_focus_out(Ref<AnimationNode> p_node); + void _node_rename_lineedit_changed(const String &p_text); + void _node_changed(const StringName &p_node_name); + String current_node_rename_text; bool updating; void _connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index); @@ -103,23 +109,26 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { void _open_in_editor(const String &p_which); void _anim_selected(int p_index, Array p_options, const String &p_node); void _delete_request(const String &p_which); - void _delete_nodes_request(); + void _delete_nodes_request(const TypedArray<StringName> &p_nodes); bool _update_filters(const Ref<AnimationNode> &anode); - void _edit_filters(const String &p_which); + void _inspect_filters(const String &p_which); void _filter_edited(); void _filter_toggled(); Ref<AnimationNode> _filter_edit; - void _popup(bool p_has_input_ports, const Vector2 &p_popup_position, const Vector2 &p_node_position); + void _popup(bool p_has_input_ports, const Vector2 &p_node_position); void _popup_request(const Vector2 &p_position); void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position); void _connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position); + void _popup_hide(); void _property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing); - void _removed_from_graph(); - EditorFileDialog *open_file; + void _update_editor_settings(); + void _update_theme(); + + EditorFileDialog *open_file = nullptr; Ref<AnimationNode> file_loaded; void _file_opened(const String &p_file); @@ -144,6 +153,8 @@ public: virtual bool can_edit(const Ref<AnimationNode> &p_node) override; virtual void edit(const Ref<AnimationNode> &p_node) override; + void update_graph(); + AnimationNodeBlendTreeEditor(); }; diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp new file mode 100644 index 0000000000..bf7e419fe4 --- /dev/null +++ b/editor/plugins/animation_library_editor.cpp @@ -0,0 +1,794 @@ +/**************************************************************************/ +/* animation_library_editor.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 "animation_library_editor.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" + +void AnimationLibraryEditor::set_animation_player(Object *p_player) { + player = p_player; +} + +void AnimationLibraryEditor::_add_library() { + add_library_dialog->set_title(TTR("Library Name:")); + add_library_name->set_text(""); + add_library_dialog->popup_centered(); + add_library_name->grab_focus(); + adding_animation = false; + adding_animation_to_library = StringName(); + _add_library_validate(""); +} + +void AnimationLibraryEditor::_add_library_validate(const String &p_name) { + String error; + + if (adding_animation) { + Ref<AnimationLibrary> al = player->call("get_animation_library", adding_animation_to_library); + ERR_FAIL_COND(al.is_null()); + if (p_name == "") { + error = TTR("Animation name can't be empty."); + } else if (!AnimationLibrary::is_valid_animation_name(p_name)) { + error = TTR("Animation name contains invalid characters: '/', ':', ',' or '['."); + } else if (al->has_animation(p_name)) { + error = TTR("Animation with the same name already exists."); + } + } else { + if (p_name == "" && bool(player->call("has_animation_library", ""))) { + error = TTR("Enter a library name."); + } else if (!AnimationLibrary::is_valid_library_name(p_name)) { + error = TTR("Library name contains invalid characters: '/', ':', ',' or '['."); + } else if (bool(player->call("has_animation_library", p_name))) { + error = TTR("Library with the same name already exists."); + } + } + + if (error != "") { + add_library_validate->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + add_library_validate->set_text(error); + add_library_dialog->get_ok_button()->set_disabled(true); + } else { + if (adding_animation) { + add_library_validate->set_text(TTR("Animation name is valid.")); + } else { + if (p_name == "") { + add_library_validate->set_text(TTR("Global library will be created.")); + } else { + add_library_validate->set_text(TTR("Library name is valid.")); + } + } + add_library_validate->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); + add_library_dialog->get_ok_button()->set_disabled(false); + } +} + +void AnimationLibraryEditor::_add_library_confirm() { + if (adding_animation) { + String anim_name = add_library_name->get_text(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + Ref<AnimationLibrary> al = player->call("get_animation_library", adding_animation_to_library); + ERR_FAIL_COND(!al.is_valid()); + + Ref<Animation> anim; + anim.instantiate(); + + undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), anim_name)); + undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, anim); + undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + + } else { + String lib_name = add_library_name->get_text(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + Ref<AnimationLibrary> al; + al.instantiate(); + + undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), lib_name)); + undo_redo->add_do_method(player, "add_animation_library", lib_name, al); + undo_redo->add_undo_method(player, "remove_animation_library", lib_name); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } +} + +void AnimationLibraryEditor::_load_library() { + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("AnimationLibrary", &extensions); + + file_dialog->set_title(TTR("Load Animation")); + file_dialog->clear_filters(); + for (const String &K : extensions) { + file_dialog->add_filter("*." + K); + } + + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + file_dialog->set_current_file(""); + file_dialog->popup_centered_ratio(); + + file_dialog_action = FILE_DIALOG_ACTION_OPEN_LIBRARY; +} + +void AnimationLibraryEditor::_file_popup_selected(int p_id) { + Ref<AnimationLibrary> al = player->call("get_animation_library", file_dialog_library); + Ref<Animation> anim; + if (file_dialog_animation != StringName()) { + anim = al->get_animation(file_dialog_animation); + ERR_FAIL_COND(anim.is_null()); + } + switch (p_id) { + case FILE_MENU_SAVE_LIBRARY: { + if (al->get_path().is_resource_file() && !FileAccess::exists(al->get_path() + ".import")) { + EditorNode::get_singleton()->save_resource(al); + break; + } + [[fallthrough]]; + } + case FILE_MENU_SAVE_AS_LIBRARY: { + // Check if we're allowed to save this + { + String al_path = al->get_path(); + if (!al_path.is_resource_file()) { + int srpos = al_path.find("::"); + if (srpos != -1) { + String base = al_path.substr(0, srpos); + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + error_dialog->set_text(TTR("This animation library can't be saved because it does not belong to the edited scene. Make it unique first.")); + error_dialog->popup_centered(); + return; + } + } + } else { + if (FileAccess::exists(al_path + ".import")) { + error_dialog->set_text(TTR("This animation library can't be saved because it was imported from another file. Make it unique first.")); + error_dialog->popup_centered(); + return; + } + } + } + + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + file_dialog->set_title(TTR("Save Library")); + if (al->get_path().is_resource_file()) { + file_dialog->set_current_path(al->get_path()); + } else { + file_dialog->set_current_file(String(file_dialog_library) + ".res"); + } + file_dialog->clear_filters(); + List<String> exts; + ResourceLoader::get_recognized_extensions_for_type("AnimationLibrary", &exts); + for (const String &K : exts) { + file_dialog->add_filter("*." + K); + } + + file_dialog->popup_centered_ratio(); + file_dialog_action = FILE_DIALOG_ACTION_SAVE_LIBRARY; + } break; + case FILE_MENU_MAKE_LIBRARY_UNIQUE: { + StringName lib_name = file_dialog_library; + List<StringName> animation_list; + + Ref<AnimationLibrary> ald = memnew(AnimationLibrary); + al->get_animation_list(&animation_list); + for (const StringName &animation_name : animation_list) { + Ref<Animation> animation = al->get_animation(animation_name); + if (EditorNode::get_singleton()->is_resource_read_only(animation)) { + animation = animation->duplicate(); + } + ald->add_animation(animation_name, animation); + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(vformat(TTR("Make Animation Library Unique: %s"), lib_name)); + undo_redo->add_do_method(player, "remove_animation_library", lib_name); + undo_redo->add_do_method(player, "add_animation_library", lib_name, ald); + undo_redo->add_undo_method(player, "remove_animation_library", lib_name); + undo_redo->add_undo_method(player, "add_animation_library", lib_name, al); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + + update_tree(); + + } break; + case FILE_MENU_EDIT_LIBRARY: { + EditorNode::get_singleton()->push_item(al.ptr()); + } break; + + case FILE_MENU_SAVE_ANIMATION: { + if (anim->get_path().is_resource_file() && !FileAccess::exists(anim->get_path() + ".import")) { + EditorNode::get_singleton()->save_resource(anim); + break; + } + [[fallthrough]]; + } + case FILE_MENU_SAVE_AS_ANIMATION: { + // Check if we're allowed to save this + { + String anim_path = al->get_path(); + if (!anim_path.is_resource_file()) { + int srpos = anim_path.find("::"); + if (srpos != -1) { + String base = anim_path.substr(0, srpos); + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + error_dialog->set_text(TTR("This animation can't be saved because it does not belong to the edited scene. Make it unique first.")); + error_dialog->popup_centered(); + return; + } + } + } else { + if (FileAccess::exists(anim_path + ".import")) { + error_dialog->set_text(TTR("This animation can't be saved because it was imported from another file. Make it unique first.")); + error_dialog->popup_centered(); + return; + } + } + } + + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + file_dialog->set_title(TTR("Save Animation")); + if (anim->get_path().is_resource_file()) { + file_dialog->set_current_path(anim->get_path()); + } else { + file_dialog->set_current_file(String(file_dialog_animation) + ".res"); + } + file_dialog->clear_filters(); + List<String> exts; + ResourceLoader::get_recognized_extensions_for_type("Animation", &exts); + for (const String &K : exts) { + file_dialog->add_filter("*." + K); + } + + file_dialog->popup_centered_ratio(); + file_dialog_action = FILE_DIALOG_ACTION_SAVE_ANIMATION; + } break; + case FILE_MENU_MAKE_ANIMATION_UNIQUE: { + StringName anim_name = file_dialog_animation; + + Ref<Animation> animd = anim->duplicate(); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(vformat(TTR("Make Animation Unique: %s"), anim_name)); + undo_redo->add_do_method(al.ptr(), "remove_animation", anim_name); + undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, animd); + undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name); + undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + + update_tree(); + } break; + case FILE_MENU_EDIT_ANIMATION: { + EditorNode::get_singleton()->push_item(anim.ptr()); + } break; + } +} +void AnimationLibraryEditor::_load_file(String p_path) { + switch (file_dialog_action) { + case FILE_DIALOG_ACTION_OPEN_LIBRARY: { + Ref<AnimationLibrary> al = ResourceLoader::load(p_path); + if (al.is_null()) { + error_dialog->set_text(TTR("Invalid AnimationLibrary file.")); + error_dialog->popup_centered(); + return; + } + + TypedArray<StringName> libs = player->call("get_animation_library_list"); + for (int i = 0; i < libs.size(); i++) { + const StringName K = libs[i]; + Ref<AnimationLibrary> al2 = player->call("get_animation_library", K); + if (al2 == al) { + error_dialog->set_text(TTR("This library is already added to the player.")); + error_dialog->popup_centered(); + + return; + } + } + + String name = AnimationLibrary::validate_library_name(p_path.get_file().get_basename()); + + int attempt = 1; + + while (bool(player->call("has_animation_library", name))) { + attempt++; + name = p_path.get_file().get_basename() + " " + itos(attempt); + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), name)); + undo_redo->add_do_method(player, "add_animation_library", name, al); + undo_redo->add_undo_method(player, "remove_animation_library", name); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } break; + case FILE_DIALOG_ACTION_OPEN_ANIMATION: { + Ref<Animation> anim = ResourceLoader::load(p_path); + if (anim.is_null()) { + error_dialog->set_text(TTR("Invalid Animation file.")); + error_dialog->popup_centered(); + return; + } + + Ref<AnimationLibrary> al = player->call("get_animation_library", adding_animation_to_library); + List<StringName> anims; + al->get_animation_list(&anims); + for (const StringName &K : anims) { + Ref<Animation> a2 = al->get_animation(K); + if (a2 == anim) { + error_dialog->set_text(TTR("This animation is already added to the library.")); + error_dialog->popup_centered(); + return; + } + } + + String name = p_path.get_file().get_basename(); + + int attempt = 1; + + while (al->has_animation(name)) { + attempt++; + name = p_path.get_file().get_basename() + " " + itos(attempt); + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + undo_redo->create_action(vformat(TTR("Load Animation into Library: %s"), name)); + undo_redo->add_do_method(al.ptr(), "add_animation", name, anim); + undo_redo->add_undo_method(al.ptr(), "remove_animation", name); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } break; + + case FILE_DIALOG_ACTION_SAVE_LIBRARY: { + Ref<AnimationLibrary> al = player->call("get_animation_library", file_dialog_library); + String prev_path = al->get_path(); + EditorNode::get_singleton()->save_resource_in_path(al, p_path); + + if (al->get_path() != prev_path) { // Save successful. + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + undo_redo->create_action(vformat(TTR("Save Animation library to File: %s"), file_dialog_library)); + undo_redo->add_do_method(al.ptr(), "set_path", al->get_path()); + undo_redo->add_undo_method(al.ptr(), "set_path", prev_path); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } + + } break; + case FILE_DIALOG_ACTION_SAVE_ANIMATION: { + Ref<AnimationLibrary> al = player->call("get_animation_library", file_dialog_library); + Ref<Animation> anim; + if (file_dialog_animation != StringName()) { + anim = al->get_animation(file_dialog_animation); + ERR_FAIL_COND(anim.is_null()); + } + String prev_path = anim->get_path(); + EditorNode::get_singleton()->save_resource_in_path(anim, p_path); + if (anim->get_path() != prev_path) { // Save successful. + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + undo_redo->create_action(vformat(TTR("Save Animation to File: %s"), file_dialog_animation)); + undo_redo->add_do_method(anim.ptr(), "set_path", anim->get_path()); + undo_redo->add_undo_method(anim.ptr(), "set_path", prev_path); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } + } break; + } +} + +void AnimationLibraryEditor::_item_renamed() { + TreeItem *ti = tree->get_edited(); + String text = ti->get_text(0); + String old_text = ti->get_metadata(0); + bool restore_text = false; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + if (String(text).contains("/") || String(text).contains(":") || String(text).contains(",") || String(text).contains("[")) { + restore_text = true; + } else { + if (ti->get_parent() == tree->get_root()) { + // Renamed library + + if (player->call("has_animation_library", text)) { + restore_text = true; + } else { + undo_redo->create_action(vformat(TTR("Rename Animation Library: %s"), text)); + undo_redo->add_do_method(player, "rename_animation_library", old_text, text); + undo_redo->add_undo_method(player, "rename_animation_library", text, old_text); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + updating = true; + undo_redo->commit_action(); + updating = false; + ti->set_metadata(0, text); + if (text == "") { + ti->set_suffix(0, TTR("[Global]")); + } else { + ti->set_suffix(0, ""); + } + } + } else { + // Renamed anim + StringName library = ti->get_parent()->get_metadata(0); + Ref<AnimationLibrary> al = player->call("get_animation_library", library); + + if (al.is_valid()) { + if (al->has_animation(text)) { + restore_text = true; + } else { + undo_redo->create_action(vformat(TTR("Rename Animation: %s"), text)); + undo_redo->add_do_method(al.ptr(), "rename_animation", old_text, text); + undo_redo->add_undo_method(al.ptr(), "rename_animation", text, old_text); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + updating = true; + undo_redo->commit_action(); + updating = false; + + ti->set_metadata(0, text); + } + } else { + restore_text = true; + } + } + } + + if (restore_text) { + ti->set_text(0, old_text); + } +} + +void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button) { + if (p_item->get_parent() == tree->get_root()) { + // Library + StringName lib_name = p_item->get_metadata(0); + Ref<AnimationLibrary> al = player->call("get_animation_library", lib_name); + switch (p_id) { + case LIB_BUTTON_ADD: { + add_library_dialog->set_title(TTR("Animation Name:")); + add_library_name->set_text(""); + add_library_dialog->popup_centered(); + add_library_name->grab_focus(); + adding_animation = true; + adding_animation_to_library = p_item->get_metadata(0); + _add_library_validate(""); + } break; + case LIB_BUTTON_LOAD: { + adding_animation_to_library = p_item->get_metadata(0); + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("Animation", &extensions); + + file_dialog->clear_filters(); + for (const String &K : extensions) { + file_dialog->add_filter("*." + K); + } + + file_dialog->set_title(TTR("Load Animation")); + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + file_dialog->set_current_file(""); + file_dialog->popup_centered_ratio(); + + file_dialog_action = FILE_DIALOG_ACTION_OPEN_ANIMATION; + + } break; + case LIB_BUTTON_PASTE: { + Ref<Animation> anim = EditorSettings::get_singleton()->get_resource_clipboard(); + if (!anim.is_valid()) { + error_dialog->set_text(TTR("No animation resource in clipboard!")); + error_dialog->popup_centered(); + return; + } + + anim = anim->duplicate(); // Users simply dont care about referencing, so making a copy works better here. + + String base_name; + if (anim->get_name() != "") { + base_name = anim->get_name(); + } else { + base_name = TTR("Pasted Animation"); + } + + String name = base_name; + int attempt = 1; + while (al->has_animation(name)) { + attempt++; + name = base_name + " (" + itos(attempt) + ")"; + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), name)); + undo_redo->add_do_method(al.ptr(), "add_animation", name, anim); + undo_redo->add_undo_method(al.ptr(), "remove_animation", name); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + + } break; + case LIB_BUTTON_FILE: { + file_popup->clear(); + file_popup->add_item(TTR("Save"), FILE_MENU_SAVE_LIBRARY); + file_popup->add_item(TTR("Save As"), FILE_MENU_SAVE_AS_LIBRARY); + file_popup->add_separator(); + file_popup->add_item(TTR("Make Unique"), FILE_MENU_MAKE_LIBRARY_UNIQUE); + file_popup->add_separator(); + file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_LIBRARY); + Rect2 pos = tree->get_item_rect(p_item, 1, 0); + Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height)); + file_popup->popup(Rect2(popup_pos, Size2())); + + file_dialog_animation = StringName(); + file_dialog_library = lib_name; + } break; + case LIB_BUTTON_DELETE: { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(vformat(TTR("Remove Animation Library: %s"), lib_name)); + undo_redo->add_do_method(player, "remove_animation_library", lib_name); + undo_redo->add_undo_method(player, "add_animation_library", lib_name, al); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } break; + } + + } else { + // Animation + StringName lib_name = p_item->get_parent()->get_metadata(0); + StringName anim_name = p_item->get_metadata(0); + Ref<AnimationLibrary> al = player->call("get_animation_library", lib_name); + Ref<Animation> anim = al->get_animation(anim_name); + ERR_FAIL_COND(!anim.is_valid()); + switch (p_id) { + case ANIM_BUTTON_COPY: { + if (anim->get_name() == "") { + anim->set_name(anim_name); // Keep the name around + } + EditorSettings::get_singleton()->set_resource_clipboard(anim); + } break; + case ANIM_BUTTON_FILE: { + file_popup->clear(); + file_popup->add_item(TTR("Save"), FILE_MENU_SAVE_ANIMATION); + file_popup->add_item(TTR("Save As"), FILE_MENU_SAVE_AS_ANIMATION); + file_popup->add_separator(); + file_popup->add_item(TTR("Make Unique"), FILE_MENU_MAKE_ANIMATION_UNIQUE); + file_popup->add_separator(); + file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_ANIMATION); + Rect2 pos = tree->get_item_rect(p_item, 1, 0); + Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height)); + file_popup->popup(Rect2(popup_pos, Size2())); + + file_dialog_animation = anim_name; + file_dialog_library = lib_name; + + } break; + case ANIM_BUTTON_DELETE: { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(vformat(TTR("Remove Animation from Library: %s"), anim_name)); + undo_redo->add_do_method(al.ptr(), "remove_animation", anim_name); + undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } break; + } + } +} + +void AnimationLibraryEditor::update_tree() { + if (updating) { + return; + } + + tree->clear(); + ERR_FAIL_COND(!player); + + Color ss_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); + + TreeItem *root = tree->create_item(); + TypedArray<StringName> libs = player->call("get_animation_library_list"); + + for (int i = 0; i < libs.size(); i++) { + const StringName K = libs[i]; + TreeItem *libitem = tree->create_item(root); + libitem->set_text(0, K); + if (K == StringName()) { + libitem->set_suffix(0, TTR("[Global]")); + } else { + libitem->set_suffix(0, ""); + } + + Ref<AnimationLibrary> al = player->call("get_animation_library", K); + bool animation_library_is_foreign = false; + String al_path = al->get_path(); + if (!al_path.is_resource_file()) { + libitem->set_text(1, TTR("[built-in]")); + libitem->set_tooltip_text(1, al_path); + int srpos = al_path.find("::"); + if (srpos != -1) { + String base = al_path.substr(0, srpos); + if (ResourceLoader::get_resource_type(base) == "PackedScene") { + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + animation_library_is_foreign = true; + libitem->set_text(1, TTR("[foreign]")); + } + } else { + if (FileAccess::exists(base + ".import")) { + animation_library_is_foreign = true; + libitem->set_text(1, TTR("[imported]")); + } + } + } + } else { + if (FileAccess::exists(al_path + ".import")) { + animation_library_is_foreign = true; + libitem->set_text(1, TTR("[imported]")); + } else { + libitem->set_text(1, al_path.get_file()); + } + } + + libitem->set_editable(0, !animation_library_is_foreign); + libitem->set_metadata(0, K); + libitem->set_icon(0, get_theme_icon("AnimationLibrary", "EditorIcons")); + + libitem->add_button(0, get_theme_icon("Add", "EditorIcons"), LIB_BUTTON_ADD, animation_library_is_foreign, TTR("Add Animation to Library")); + libitem->add_button(0, get_theme_icon("Load", "EditorIcons"), LIB_BUTTON_LOAD, animation_library_is_foreign, TTR("Load animation from file and add to library")); + libitem->add_button(0, get_theme_icon("ActionPaste", "EditorIcons"), LIB_BUTTON_PASTE, animation_library_is_foreign, TTR("Paste Animation to Library from clipboard")); + + libitem->add_button(1, get_theme_icon("Save", "EditorIcons"), LIB_BUTTON_FILE, false, TTR("Save animation library to resource on disk")); + libitem->add_button(1, get_theme_icon("Remove", "EditorIcons"), LIB_BUTTON_DELETE, false, TTR("Remove animation library")); + + libitem->set_custom_bg_color(0, ss_color); + + List<StringName> animations; + al->get_animation_list(&animations); + for (const StringName &L : animations) { + TreeItem *anitem = tree->create_item(libitem); + anitem->set_text(0, L); + anitem->set_editable(0, !animation_library_is_foreign); + anitem->set_metadata(0, L); + anitem->set_icon(0, get_theme_icon("Animation", "EditorIcons")); + anitem->add_button(0, get_theme_icon("ActionCopy", "EditorIcons"), ANIM_BUTTON_COPY, animation_library_is_foreign, TTR("Copy animation to clipboard")); + + Ref<Animation> anim = al->get_animation(L); + String anim_path = anim->get_path(); + if (!anim_path.is_resource_file()) { + anitem->set_text(1, TTR("[built-in]")); + anitem->set_tooltip_text(1, anim_path); + int srpos = anim_path.find("::"); + if (srpos != -1) { + String base = anim_path.substr(0, srpos); + if (ResourceLoader::get_resource_type(base) == "PackedScene") { + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + anitem->set_text(1, TTR("[foreign]")); + } + } else { + if (FileAccess::exists(base + ".import")) { + anitem->set_text(1, TTR("[imported]")); + } + } + } + } else { + if (FileAccess::exists(anim_path + ".import")) { + anitem->set_text(1, TTR("[imported]")); + } else { + anitem->set_text(1, anim_path.get_file()); + } + } + anitem->add_button(1, get_theme_icon("Save", "EditorIcons"), ANIM_BUTTON_FILE, animation_library_is_foreign, TTR("Save animation to resource on disk")); + anitem->add_button(1, get_theme_icon("Remove", "EditorIcons"), ANIM_BUTTON_DELETE, animation_library_is_foreign, TTR("Remove animation from Library")); + } + } +} + +void AnimationLibraryEditor::show_dialog() { + update_tree(); + popup_centered_ratio(0.5); +} + +void AnimationLibraryEditor::_update_editor(Object *p_player) { + emit_signal("update_editor", p_player); +} + +void AnimationLibraryEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_update_editor", "player"), &AnimationLibraryEditor::_update_editor); + ADD_SIGNAL(MethodInfo("update_editor")); +} + +AnimationLibraryEditor::AnimationLibraryEditor() { + set_title(TTR("Edit Animation Libraries")); + + file_dialog = memnew(EditorFileDialog); + add_child(file_dialog); + file_dialog->connect("file_selected", callable_mp(this, &AnimationLibraryEditor::_load_file)); + + add_library_dialog = memnew(ConfirmationDialog); + VBoxContainer *dialog_vb = memnew(VBoxContainer); + add_library_name = memnew(LineEdit); + dialog_vb->add_child(add_library_name); + add_library_name->connect("text_changed", callable_mp(this, &AnimationLibraryEditor::_add_library_validate)); + add_child(add_library_dialog); + + add_library_validate = memnew(Label); + dialog_vb->add_child(add_library_validate); + add_library_dialog->add_child(dialog_vb); + add_library_dialog->connect("confirmed", callable_mp(this, &AnimationLibraryEditor::_add_library_confirm)); + add_library_dialog->register_text_enter(add_library_name); + + VBoxContainer *vb = memnew(VBoxContainer); + HBoxContainer *hb = memnew(HBoxContainer); + hb->add_spacer(true); + Button *b = memnew(Button(TTR("Add Library"))); + b->connect("pressed", callable_mp(this, &AnimationLibraryEditor::_add_library)); + hb->add_child(b); + b = memnew(Button(TTR("Load Library"))); + b->connect("pressed", callable_mp(this, &AnimationLibraryEditor::_load_library)); + hb->add_child(b); + vb->add_child(hb); + tree = memnew(Tree); + vb->add_child(tree); + + tree->set_columns(2); + tree->set_column_titles_visible(true); + tree->set_column_title(0, TTR("Resource")); + tree->set_column_title(1, TTR("Storage")); + tree->set_column_expand(0, true); + tree->set_column_custom_minimum_width(1, EDSCALE * 250); + tree->set_column_expand(1, false); + tree->set_hide_root(true); + tree->set_hide_folding(true); + tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + tree->connect("item_edited", callable_mp(this, &AnimationLibraryEditor::_item_renamed)); + tree->connect("button_clicked", callable_mp(this, &AnimationLibraryEditor::_button_pressed)); + + file_popup = memnew(PopupMenu); + add_child(file_popup); + file_popup->connect("id_pressed", callable_mp(this, &AnimationLibraryEditor::_file_popup_selected)); + + add_child(vb); + + error_dialog = memnew(AcceptDialog); + error_dialog->set_title(TTR("Error:")); + add_child(error_dialog); +} diff --git a/editor/plugins/animation_library_editor.h b/editor/plugins/animation_library_editor.h new file mode 100644 index 0000000000..a9b2c72b9d --- /dev/null +++ b/editor/plugins/animation_library_editor.h @@ -0,0 +1,119 @@ +/**************************************************************************/ +/* animation_library_editor.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 ANIMATION_LIBRARY_EDITOR_H +#define ANIMATION_LIBRARY_EDITOR_H + +#include "editor/animation_track_editor.h" +#include "editor/editor_plugin.h" +#include "scene/animation/animation_player.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/tree.h" + +class EditorFileDialog; + +class AnimationLibraryEditor : public AcceptDialog { + GDCLASS(AnimationLibraryEditor, AcceptDialog) + + enum { + LIB_BUTTON_ADD, + LIB_BUTTON_LOAD, + LIB_BUTTON_PASTE, + LIB_BUTTON_FILE, + LIB_BUTTON_DELETE, + }; + enum { + ANIM_BUTTON_COPY, + ANIM_BUTTON_FILE, + ANIM_BUTTON_DELETE, + }; + + enum FileMenuAction { + FILE_MENU_SAVE_LIBRARY, + FILE_MENU_SAVE_AS_LIBRARY, + FILE_MENU_MAKE_LIBRARY_UNIQUE, + FILE_MENU_EDIT_LIBRARY, + + FILE_MENU_SAVE_ANIMATION, + FILE_MENU_SAVE_AS_ANIMATION, + FILE_MENU_MAKE_ANIMATION_UNIQUE, + FILE_MENU_EDIT_ANIMATION, + }; + + enum FileDialogAction { + FILE_DIALOG_ACTION_OPEN_LIBRARY, + FILE_DIALOG_ACTION_SAVE_LIBRARY, + FILE_DIALOG_ACTION_OPEN_ANIMATION, + FILE_DIALOG_ACTION_SAVE_ANIMATION, + }; + + FileDialogAction file_dialog_action = FILE_DIALOG_ACTION_OPEN_ANIMATION; + + StringName file_dialog_animation; + StringName file_dialog_library; + + AcceptDialog *error_dialog = nullptr; + bool adding_animation = false; + StringName adding_animation_to_library; + EditorFileDialog *file_dialog = nullptr; + ConfirmationDialog *add_library_dialog = nullptr; + LineEdit *add_library_name = nullptr; + Label *add_library_validate = nullptr; + PopupMenu *file_popup = nullptr; + + Tree *tree = nullptr; + + Object *player = nullptr; + + void _add_library(); + void _add_library_validate(const String &p_name); + void _add_library_confirm(); + void _load_library(); + void _load_file(String p_path); + + void _item_renamed(); + void _button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button); + + void _file_popup_selected(int p_id); + + bool updating = false; + +protected: + void _update_editor(Object *p_player); + static void _bind_methods(); + +public: + void set_animation_player(Object *p_player); + void show_dialog(); + void update_tree(); + AnimationLibraryEditor(); +}; + +#endif // ANIMATION_LIBRARY_EDITOR_H diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 4ce9f40a5e..b33ad67f23 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* animation_player_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_player_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 "animation_player_editor_plugin.h" @@ -35,23 +35,30 @@ #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/os/keyboard.h" -#include "editor/animation_track_editor.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/inspector_dock.h" #include "editor/plugins/canvas_item_editor_plugin.h" // For onion skinning. #include "editor/plugins/node_3d_editor_plugin.h" // For onion skinning. +#include "editor/scene_tree_dock.h" +#include "scene/gui/separator.h" #include "scene/main/window.h" #include "scene/resources/animation.h" #include "scene/scene_string_names.h" #include "servers/rendering_server.h" +/////////////////////////////////// + void AnimationPlayerEditor::_node_removed(Node *p_node) { if (player && player == p_node) { player = nullptr; set_process(false); - track_editor->set_animation(Ref<Animation>()); + track_editor->set_animation(Ref<Animation>(), true); track_editor->set_root(nullptr); track_editor->show_select_node_warning(true); _update_player(); @@ -80,18 +87,20 @@ void AnimationPlayerEditor::_notification(int p_what) { } frame->set_value(player->get_current_animation_position()); track_editor->set_anim_pos(player->get_current_animation_position()); - } else if (!player->is_valid()) { // Reset timeline when the player has been stopped externally frame->set_value(0); } else if (last_active) { // Need the last frame after it stopped. frame->set_value(player->get_current_animation_position()); + track_editor->set_anim_pos(player->get_current_animation_position()); + stop->set_icon(stop_icon); } last_active = player->is_playing(); updating = false; } break; + case NOTIFICATION_ENTER_TREE: { tool_anim->get_popup()->connect("id_pressed", callable_mp(this, &AnimationPlayerEditor::_animation_tool_menu)); @@ -101,16 +110,25 @@ void AnimationPlayerEditor::_notification(int p_what) { get_tree()->connect("node_removed", callable_mp(this, &AnimationPlayerEditor::_node_removed)); - add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); + add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); + add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); } break; + case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { - autoplay->set_icon(get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons"))); + stop_icon = get_theme_icon(SNAME("Stop"), SNAME("EditorIcons")); + pause_icon = get_theme_icon(SNAME("Pause"), SNAME("EditorIcons")); + if (player && player->is_playing()) { + stop->set_icon(pause_icon); + } else { + stop->set_icon(stop_icon); + } + autoplay->set_icon(get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons"))); play->set_icon(get_theme_icon(SNAME("PlayStart"), SNAME("EditorIcons"))); play_from->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); play_bw->set_icon(get_theme_icon(SNAME("PlayStartBackwards"), SNAME("EditorIcons"))); @@ -121,16 +139,12 @@ void AnimationPlayerEditor::_notification(int p_what) { { Ref<Image> autoplay_img = autoplay_icon->get_image(); Ref<Image> reset_img = reset_icon->get_image(); - Ref<Image> autoplay_reset_img; Size2 icon_size = autoplay_img->get_size(); - autoplay_reset_img.instantiate(); - autoplay_reset_img->create(icon_size.x * 2, icon_size.y, false, autoplay_img->get_format()); - autoplay_reset_img->blit_rect(autoplay_img, Rect2(Point2(), icon_size), Point2()); - autoplay_reset_img->blit_rect(reset_img, Rect2(Point2(), icon_size), Point2(icon_size.x, 0)); - autoplay_reset_icon.instantiate(); - autoplay_reset_icon->create_from_image(autoplay_reset_img); + Ref<Image> autoplay_reset_img = Image::create_empty(icon_size.x * 2, icon_size.y, false, autoplay_img->get_format()); + autoplay_reset_img->blit_rect(autoplay_img, Rect2i(Point2i(), icon_size), Point2i()); + autoplay_reset_img->blit_rect(reset_img, Rect2i(Point2i(), icon_size), Point2i(icon_size.x, 0)); + autoplay_reset_icon = ImageTexture::create_from_image(autoplay_reset_img); } - stop->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); onion_toggle->set_icon(get_theme_icon(SNAME("Onion"), SNAME("EditorIcons"))); onion_skinning->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); @@ -143,14 +157,14 @@ void AnimationPlayerEditor::_notification(int p_what) { #define ITEM_ICON(m_item, m_icon) tool_anim->get_popup()->set_item_icon(tool_anim->get_popup()->get_item_index(m_item), get_theme_icon(SNAME(m_icon), SNAME("EditorIcons"))) ITEM_ICON(TOOL_NEW_ANIM, "New"); - ITEM_ICON(TOOL_LOAD_ANIM, "Load"); - ITEM_ICON(TOOL_SAVE_ANIM, "Save"); - ITEM_ICON(TOOL_SAVE_AS_ANIM, "Save"); + ITEM_ICON(TOOL_ANIM_LIBRARY, "AnimationLibrary"); ITEM_ICON(TOOL_DUPLICATE_ANIM, "Duplicate"); ITEM_ICON(TOOL_RENAME_ANIM, "Rename"); ITEM_ICON(TOOL_EDIT_TRANSITIONS, "Blend"); ITEM_ICON(TOOL_EDIT_RESOURCE, "Edit"); ITEM_ICON(TOOL_REMOVE_ANIM, "Remove"); + + _update_animation_list_icons(); } break; } } @@ -159,10 +173,11 @@ void AnimationPlayerEditor::_autoplay_pressed() { if (updating) { return; } - if (animation->get_item_count() == 0) { + if (animation->has_selectable_items() == 0) { return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); String current = animation->get_item_text(animation->get_selected()); if (player->get_autoplay() == current) { //unset @@ -185,78 +200,73 @@ void AnimationPlayerEditor::_autoplay_pressed() { } void AnimationPlayerEditor::_play_pressed() { - String current; - if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) { - current = animation->get_item_text(animation->get_selected()); - } + String current = _get_current(); if (!current.is_empty()) { if (current == player->get_assigned_animation()) { player->stop(); //so it won't blend with itself } + ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); player->play(current); } //unstop - stop->set_pressed(false); + stop->set_icon(pause_icon); } void AnimationPlayerEditor::_play_from_pressed() { - String current; - if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) { - current = animation->get_item_text(animation->get_selected()); - } + String current = _get_current(); if (!current.is_empty()) { float time = player->get_current_animation_position(); - if (current == player->get_assigned_animation() && player->is_playing()) { player->stop(); //so it won't blend with itself } - - player->play(current); + ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); player->seek(time); + player->play(current); } //unstop - stop->set_pressed(false); + stop->set_icon(pause_icon); } -void AnimationPlayerEditor::_play_bw_pressed() { +String AnimationPlayerEditor::_get_current() const { String current; - if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) { + if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count() && !animation->is_item_separator(animation->get_selected())) { current = animation->get_item_text(animation->get_selected()); } - + return current; +} +void AnimationPlayerEditor::_play_bw_pressed() { + String current = _get_current(); if (!current.is_empty()) { if (current == player->get_assigned_animation()) { player->stop(); //so it won't blend with itself } - player->play(current, -1, -1, true); + ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); + player->play_backwards(current); } //unstop - stop->set_pressed(false); + stop->set_icon(pause_icon); } void AnimationPlayerEditor::_play_bw_from_pressed() { - String current; - if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) { - current = animation->get_item_text(animation->get_selected()); - } + String current = _get_current(); if (!current.is_empty()) { float time = player->get_current_animation_position(); if (current == player->get_assigned_animation()) { player->stop(); //so it won't blend with itself } - - player->play(current, -1, -1, true); + ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); player->seek(time); + player->play_backwards(current); } //unstop - stop->set_pressed(false); + stop->set_icon(pause_icon); } void AnimationPlayerEditor::_stop_pressed() { @@ -264,9 +274,16 @@ void AnimationPlayerEditor::_stop_pressed() { return; } - player->stop(false); - play->set_pressed(false); - stop->set_pressed(true); + if (player->is_playing()) { + player->pause(); + } else { + String current = _get_current(); + player->stop(); + player->set_assigned_animation(current); + frame->set_value(0); + track_editor->set_anim_pos(0); + } + stop->set_icon(stop_icon); } void AnimationPlayerEditor::_animation_selected(int p_which) { @@ -275,17 +292,16 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { } // when selecting an animation, the idea is that the only interesting behavior // ui-wise is that it should play/blend the next one if currently playing - String current; - if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) { - current = animation->get_item_text(animation->get_selected()); - } + String current = _get_current(); if (!current.is_empty()) { player->set_assigned_animation(current); Ref<Animation> anim = player->get_animation(current); { - track_editor->set_animation(anim); + bool animation_library_is_foreign = EditorNode::get_singleton()->is_resource_read_only(anim); + + track_editor->set_animation(anim, animation_library_is_foreign); Node *root = player->get_node(player->get_root()); if (root) { track_editor->set_root(root); @@ -294,29 +310,36 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { frame->set_max((double)anim->get_length()); } else { - track_editor->set_animation(Ref<Animation>()); + track_editor->set_animation(Ref<Animation>(), true); track_editor->set_root(nullptr); } autoplay->set_pressed(current == player->get_autoplay()); AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); - EditorNode::get_singleton()->update_keying(); _animation_key_editor_seek(timeline_position, false); + + emit_signal("animation_selected", current); } void AnimationPlayerEditor::_animation_new() { - renaming = false; - name_title->set_text(TTR("New Animation Name:")); - int count = 1; - String base = TTR("New Anim"); + String base = "new_animation"; + String current_library_name = ""; + if (animation->has_selectable_items()) { + String current_animation_name = animation->get_item_text(animation->get_selected()); + Ref<Animation> current_animation = player->get_animation(current_animation_name); + if (current_animation.is_valid()) { + current_library_name = player->find_animation_library(current_animation); + } + } + String attempt_prefix = (current_library_name == "") ? "" : current_library_name + "/"; while (true) { String attempt = base; if (count > 1) { - attempt += " (" + itos(count) + ")"; + attempt += vformat("_%d", count); } - if (player->has_animation(attempt)) { + if (player->has_animation(attempt_prefix + attempt)) { count++; continue; } @@ -324,103 +347,41 @@ void AnimationPlayerEditor::_animation_new() { break; } - name->set_text(base); + _update_name_dialog_library_dropdown(); + + name_dialog_op = TOOL_NEW_ANIM; + name_dialog->set_title(TTR("Create New Animation")); name_dialog->popup_centered(Size2(300, 90)); + name_title->set_text(TTR("New Animation Name:")); + name->set_text(base); name->select_all(); name->grab_focus(); } void AnimationPlayerEditor::_animation_rename() { - if (animation->get_item_count() == 0) { + if (!animation->has_selectable_items()) { return; } int selected = animation->get_selected(); String selected_name = animation->get_item_text(selected); + // Remove library prefix if present. + if (selected_name.contains("/")) { + selected_name = selected_name.get_slice("/", 1); + } + + name_dialog->set_title(TTR("Rename Animation")); name_title->set_text(TTR("Change Animation Name:")); name->set_text(selected_name); - renaming = true; + name_dialog_op = TOOL_RENAME_ANIM; name_dialog->popup_centered(Size2(300, 90)); name->select_all(); name->grab_focus(); -} - -void AnimationPlayerEditor::_animation_load() { - ERR_FAIL_COND(!player); - file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES); - file->clear_filters(); - List<String> extensions; - - ResourceLoader::get_recognized_extensions_for_type("Animation", &extensions); - for (const String &E : extensions) { - file->add_filter("*." + E + " ; " + E.to_upper()); - } - - file->popup_file_dialog(); -} - -void AnimationPlayerEditor::_animation_save_in_path(const Ref<Resource> &p_resource, const String &p_path) { - int flg = 0; - if (EditorSettings::get_singleton()->get("filesystem/on_save/compress_binary_resources")) { - flg |= ResourceSaver::FLAG_COMPRESS; - } - - String path = ProjectSettings::get_singleton()->localize_path(p_path); - Error err = ResourceSaver::save(path, p_resource, flg | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS); - - if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Error saving resource!")); - return; - } - - ((Resource *)p_resource.ptr())->set_path(path); - editor->emit_signal(SNAME("resource_saved"), p_resource); -} - -void AnimationPlayerEditor::_animation_save(const Ref<Resource> &p_resource) { - if (p_resource->get_path().is_resource_file()) { - _animation_save_in_path(p_resource, p_resource->get_path()); - } else { - _animation_save_as(p_resource); - } -} - -void AnimationPlayerEditor::_animation_save_as(const Ref<Resource> &p_resource) { - file->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); - - List<String> extensions; - ResourceSaver::get_recognized_extensions(p_resource, &extensions); - file->clear_filters(); - for (int i = 0; i < extensions.size(); i++) { - file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); - } - - String path; - //file->set_current_path(current_path); - if (!p_resource->get_path().is_empty()) { - path = p_resource->get_path(); - if (extensions.size()) { - if (extensions.find(p_resource->get_path().get_extension().to_lower()) == nullptr) { - path = p_resource->get_path().get_base_dir() + p_resource->get_name() + "." + extensions.front()->get(); - } - } - } else { - if (extensions.size()) { - if (!p_resource->get_name().is_empty()) { - path = p_resource->get_name() + "." + extensions.front()->get().to_lower(); - } else { - String resource_name_snake_case = p_resource->get_class().camelcase_to_underscore(); - path = "new_" + resource_name_snake_case + "." + extensions.front()->get().to_lower(); - } - } - } - file->set_current_path(path); - file->set_title(TTR("Save Resource As...")); - file->popup_file_dialog(); + library->hide(); } void AnimationPlayerEditor::_animation_remove() { - if (animation->get_item_count() == 0) { + if (!animation->has_selectable_items()) { return; } @@ -434,6 +395,14 @@ void AnimationPlayerEditor::_animation_remove_confirmed() { String current = animation->get_item_text(animation->get_selected()); Ref<Animation> anim = player->get_animation(current); + Ref<AnimationLibrary> al = player->get_animation_library(player->find_animation_library(anim)); + ERR_FAIL_COND(al.is_null()); + + // For names of form lib_name/anim_name, remove library name prefix. + if (current.contains("/")) { + current = current.get_slice("/", 1); + } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove Animation")); if (player->get_autoplay() == current) { undo_redo->add_do_method(player, "set_autoplay", ""); @@ -441,11 +410,11 @@ void AnimationPlayerEditor::_animation_remove_confirmed() { // Avoid having the autoplay icon linger around if there is only one animation in the player. undo_redo->add_do_method(this, "_animation_player_changed", player); } - undo_redo->add_do_method(player, "remove_animation", current); - undo_redo->add_undo_method(player, "add_animation", current, anim); + undo_redo->add_do_method(al.ptr(), "remove_animation", current); + undo_redo->add_undo_method(al.ptr(), "add_animation", current, anim); undo_redo->add_do_method(this, "_animation_player_changed", player); undo_redo->add_undo_method(this, "_animation_player_changed", player); - if (animation->get_item_count() == 1) { + if (animation->has_selectable_items() && animation->get_selectable_item(false) == animation->get_selectable_item(true)) { // Last item remaining. undo_redo->add_do_method(this, "_stop_onion_skinning"); undo_redo->add_undo_method(this, "_start_onion_skinning"); } @@ -468,7 +437,7 @@ void AnimationPlayerEditor::_select_anim_by_name(const String &p_anim) { _animation_selected(idx); } -double AnimationPlayerEditor::_get_editor_step() const { +float AnimationPlayerEditor::_get_editor_step() const { // Returns the effective snapping value depending on snapping modifiers, or 0 if snapping is disabled. if (track_editor->is_snap_enabled()) { const String current = player->get_assigned_animation(); @@ -479,73 +448,174 @@ double AnimationPlayerEditor::_get_editor_step() const { return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step(); } - return 0.0; + return 0.0f; } void AnimationPlayerEditor::_animation_name_edited() { - player->stop(); + if (player->is_playing()) { + player->stop(); + } String new_name = name->get_text(); - if (new_name.is_empty() || new_name.find(":") != -1 || new_name.find("/") != -1) { + if (!AnimationLibrary::is_valid_animation_name(new_name)) { error_dialog->set_text(TTR("Invalid animation name!")); error_dialog->popup_centered(); return; } - if (renaming && animation->get_item_count() > 0 && animation->get_item_text(animation->get_selected()) == new_name) { + if (name_dialog_op == TOOL_RENAME_ANIM && animation->has_selectable_items() && animation->get_item_text(animation->get_selected()) == new_name) { name_dialog->hide(); return; } - if (player->has_animation(new_name)) { - error_dialog->set_text(TTR("Animation name already exists!")); + String test_name_prefix = ""; + if (library->is_visible() && library->get_selected_id() != -1) { + test_name_prefix = library->get_item_metadata(library->get_selected_id()); + test_name_prefix += (test_name_prefix != "") ? "/" : ""; + } + + if (player->has_animation(test_name_prefix + new_name)) { + error_dialog->set_text(vformat(TTR("Animation '%s' already exists!"), test_name_prefix + new_name)); error_dialog->popup_centered(); return; } - if (renaming) { - String current = animation->get_item_text(animation->get_selected()); - Ref<Animation> anim = player->get_animation(current); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + switch (name_dialog_op) { + case TOOL_RENAME_ANIM: { + String current = animation->get_item_text(animation->get_selected()); + Ref<Animation> anim = player->get_animation(current); - undo_redo->create_action(TTR("Rename Animation")); - undo_redo->add_do_method(player, "rename_animation", current, new_name); - undo_redo->add_do_method(anim.ptr(), "set_name", new_name); - undo_redo->add_undo_method(player, "rename_animation", new_name, current); - undo_redo->add_undo_method(anim.ptr(), "set_name", current); - undo_redo->add_do_method(this, "_animation_player_changed", player); - undo_redo->add_undo_method(this, "_animation_player_changed", player); - undo_redo->commit_action(); + Ref<AnimationLibrary> al = player->get_animation_library(player->find_animation_library(anim)); + ERR_FAIL_COND(al.is_null()); - _select_anim_by_name(new_name); + // Extract library prefix if present. + String new_library_prefix = ""; + if (current.contains("/")) { + new_library_prefix = current.get_slice("/", 0) + "/"; + current = current.get_slice("/", 1); + } - } else { - Ref<Animation> new_anim = Ref<Animation>(memnew(Animation)); - new_anim->set_name(new_name); + undo_redo->create_action(TTR("Rename Animation")); + undo_redo->add_do_method(al.ptr(), "rename_animation", current, new_name); + undo_redo->add_do_method(anim.ptr(), "set_name", new_name); + undo_redo->add_undo_method(al.ptr(), "rename_animation", new_name, current); + undo_redo->add_undo_method(anim.ptr(), "set_name", current); + undo_redo->add_do_method(this, "_animation_player_changed", player); + undo_redo->add_undo_method(this, "_animation_player_changed", player); + undo_redo->commit_action(); - undo_redo->create_action(TTR("Add Animation")); - undo_redo->add_do_method(player, "add_animation", new_name, new_anim); - undo_redo->add_undo_method(player, "remove_animation", new_name); - undo_redo->add_do_method(this, "_animation_player_changed", player); - undo_redo->add_undo_method(this, "_animation_player_changed", player); - if (animation->get_item_count() == 0) { - undo_redo->add_do_method(this, "_start_onion_skinning"); - undo_redo->add_undo_method(this, "_stop_onion_skinning"); - } - undo_redo->commit_action(); + _select_anim_by_name(new_library_prefix + new_name); + } break; + + case TOOL_NEW_ANIM: { + Ref<Animation> new_anim = Ref<Animation>(memnew(Animation)); + new_anim->set_name(new_name); + String library_name; + Ref<AnimationLibrary> al; + if (library->is_visible()) { + library_name = library->get_item_metadata(library->get_selected()); + // It's possible that [Global] was selected, but doesn't exist yet. + if (player->has_animation_library(library_name)) { + al = player->get_animation_library(library_name); + } + + } else { + if (player->has_animation_library("")) { + al = player->get_animation_library(""); + library_name = ""; + } + } + + undo_redo->create_action(TTR("Add Animation")); + + bool lib_added = false; + if (al.is_null()) { + al.instantiate(); + lib_added = true; + undo_redo->add_do_method(player, "add_animation_library", "", al); + library_name = ""; + } - _select_anim_by_name(new_name); + undo_redo->add_do_method(al.ptr(), "add_animation", new_name, new_anim); + undo_redo->add_undo_method(al.ptr(), "remove_animation", new_name); + undo_redo->add_do_method(this, "_animation_player_changed", player); + undo_redo->add_undo_method(this, "_animation_player_changed", player); + if (!animation->has_selectable_items()) { + undo_redo->add_do_method(this, "_start_onion_skinning"); + undo_redo->add_undo_method(this, "_stop_onion_skinning"); + } + if (lib_added) { + undo_redo->add_undo_method(player, "remove_animation_library", ""); + } + undo_redo->commit_action(); + + if (library_name != "") { + library_name = library_name + "/"; + } + _select_anim_by_name(library_name + new_name); + + } break; + + case TOOL_DUPLICATE_ANIM: { + String current = animation->get_item_text(animation->get_selected()); + Ref<Animation> anim = player->get_animation(current); + + Ref<Animation> new_anim = _animation_clone(anim); + new_anim->set_name(new_name); + + String library_name; + Ref<AnimationLibrary> al; + if (library->is_visible()) { + library_name = library->get_item_metadata(library->get_selected()); + // It's possible that [Global] was selected, but doesn't exist yet. + if (player->has_animation_library(library_name)) { + al = player->get_animation_library(library_name); + } + } else { + if (player->has_animation_library("")) { + al = player->get_animation_library(""); + library_name = ""; + } + } + + undo_redo->create_action(TTR("Duplicate Animation")); + + bool lib_added = false; + if (al.is_null()) { + al.instantiate(); + lib_added = true; + undo_redo->add_do_method(player, "add_animation_library", "", al); + library_name = ""; + } + + undo_redo->add_do_method(al.ptr(), "add_animation", new_name, new_anim); + undo_redo->add_undo_method(al.ptr(), "remove_animation", new_name); + undo_redo->add_do_method(this, "_animation_player_changed", player); + undo_redo->add_undo_method(this, "_animation_player_changed", player); + if (lib_added) { + undo_redo->add_undo_method(player, "remove_animation_library", ""); + } + undo_redo->commit_action(); + + if (library_name != "") { + library_name = library_name + "/"; + } + _select_anim_by_name(library_name + new_name); + } break; } name_dialog->hide(); } void AnimationPlayerEditor::_blend_editor_next_changed(const int p_idx) { - if (animation->get_item_count() == 0) { + if (!animation->has_selectable_items()) { return; } String current = animation->get_item_text(animation->get_selected()); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Blend Next Changed")); undo_redo->add_do_method(player, "animation_set_next", current, blend_editor.next->get_item_text(p_idx)); undo_redo->add_undo_method(player, "animation_set_next", current, player->animation_get_next(current)); @@ -561,7 +631,7 @@ void AnimationPlayerEditor::_animation_blend() { blend_editor.tree->clear(); - if (animation->get_item_count() == 0) { + if (!animation->has_selectable_items()) { return; } @@ -616,7 +686,7 @@ void AnimationPlayerEditor::_blend_edited() { return; } - if (animation->get_item_count() == 0) { + if (!animation->has_selectable_items()) { return; } @@ -632,6 +702,7 @@ void AnimationPlayerEditor::_blend_edited() { float blend_time = selected->get_range(1); float prev_blend_time = player->get_blend_time(current, to); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Change Blend Time")); undo_redo->add_do_method(player, "set_blend_time", current, to, blend_time); undo_redo->add_undo_method(player, "set_blend_time", current, to, prev_blend_time); @@ -673,9 +744,20 @@ void AnimationPlayerEditor::set_state(const Dictionary &p_state) { if (p_state.has("player")) { Node *n = EditorNode::get_singleton()->get_edited_scene()->get_node(p_state["player"]); if (Object::cast_to<AnimationPlayer>(n) && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) { + if (player) { + if (player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) { + player->disconnect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated)); + } + } player = Object::cast_to<AnimationPlayer>(n); + if (player) { + if (!player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) { + player->connect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated)); + } + } + _update_player(); - editor->make_bottom_panel_item_visible(this); + EditorNode::get_singleton()->make_bottom_panel_item_visible(this); set_process(true); ensure_visibility(); @@ -695,74 +777,32 @@ void AnimationPlayerEditor::set_state(const Dictionary &p_state) { } void AnimationPlayerEditor::_animation_resource_edit() { - if (animation->get_item_count()) { - String current = animation->get_item_text(animation->get_selected()); + String current = _get_current(); + if (current != String()) { Ref<Animation> anim = player->get_animation(current); - editor->edit_resource(anim); + EditorNode::get_singleton()->edit_resource(anim); } } void AnimationPlayerEditor::_animation_edit() { - if (animation->get_item_count()) { - String current = animation->get_item_text(animation->get_selected()); + String current = _get_current(); + if (current != String()) { Ref<Animation> anim = player->get_animation(current); - track_editor->set_animation(anim); + + bool animation_library_is_foreign = EditorNode::get_singleton()->is_resource_read_only(anim); + + track_editor->set_animation(anim, animation_library_is_foreign); Node *root = player->get_node(player->get_root()); if (root) { track_editor->set_root(root); } } else { - track_editor->set_animation(Ref<Animation>()); + track_editor->set_animation(Ref<Animation>(), true); track_editor->set_root(nullptr); } } -void AnimationPlayerEditor::_save_animation(String p_file) { - String current = animation->get_item_text(animation->get_selected()); - if (!current.is_empty()) { - Ref<Animation> anim = player->get_animation(current); - - ERR_FAIL_COND(!Object::cast_to<Resource>(*anim)); - - RES current_res = RES(Object::cast_to<Resource>(*anim)); - - _animation_save_in_path(current_res, p_file); - } -} - -void AnimationPlayerEditor::_load_animations(Vector<String> p_files) { - ERR_FAIL_COND(!player); - - for (int i = 0; i < p_files.size(); i++) { - String file = p_files[i]; - - Ref<Resource> res = ResourceLoader::load(file, "Animation"); - ERR_FAIL_COND_MSG(res.is_null(), "Cannot load Animation from file '" + file + "'."); - ERR_FAIL_COND_MSG(!res->is_class("Animation"), "Loaded resource from file '" + file + "' is not Animation."); - if (file.rfind("/") != -1) { - file = file.substr(file.rfind("/") + 1, file.length()); - } - if (file.rfind("\\") != -1) { - file = file.substr(file.rfind("\\") + 1, file.length()); - } - - if (file.find(".") != -1) { - file = file.substr(0, file.find(".")); - } - - undo_redo->create_action(TTR("Load Animation")); - undo_redo->add_do_method(player, "add_animation", file, res); - undo_redo->add_undo_method(player, "remove_animation", file); - if (player->has_animation(file)) { - undo_redo->add_undo_method(player, "add_animation", file, player->get_animation(file)); - } - undo_redo->add_do_method(this, "_animation_player_changed", player); - undo_redo->add_undo_method(this, "_animation_player_changed", player); - undo_redo->commit_action(); - } -} - void AnimationPlayerEditor::_scale_changed(const String &p_scale) { player->set_speed_scale(p_scale.to_float()); } @@ -774,12 +814,9 @@ void AnimationPlayerEditor::_update_animation() { updating = true; if (player->is_playing()) { - play->set_pressed(true); - stop->set_pressed(false); - + stop->set_icon(pause_icon); } else { - play->set_pressed(false); - stop->set_pressed(true); + stop->set_icon(stop_icon); } scale->set_text(String::num(player->get_speed_scale(), 2)); @@ -797,79 +834,106 @@ void AnimationPlayerEditor::_update_animation() { void AnimationPlayerEditor::_update_player() { updating = true; - List<StringName> animlist; - if (player) { - player->get_animation_list(&animlist); - } animation->clear(); -#define ITEM_DISABLED(m_item, m_disabled) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), m_disabled) - - ITEM_DISABLED(TOOL_SAVE_ANIM, animlist.size() == 0); - ITEM_DISABLED(TOOL_SAVE_AS_ANIM, animlist.size() == 0); - ITEM_DISABLED(TOOL_DUPLICATE_ANIM, animlist.size() == 0); - ITEM_DISABLED(TOOL_RENAME_ANIM, animlist.size() == 0); - ITEM_DISABLED(TOOL_EDIT_TRANSITIONS, animlist.size() == 0); - ITEM_DISABLED(TOOL_COPY_ANIM, animlist.size() == 0); - ITEM_DISABLED(TOOL_REMOVE_ANIM, animlist.size() == 0); - - stop->set_disabled(animlist.size() == 0); - play->set_disabled(animlist.size() == 0); - play_bw->set_disabled(animlist.size() == 0); - play_bw_from->set_disabled(animlist.size() == 0); - play_from->set_disabled(animlist.size() == 0); - frame->set_editable(animlist.size() != 0); - animation->set_disabled(animlist.size() == 0); - autoplay->set_disabled(animlist.size() == 0); tool_anim->set_disabled(player == nullptr); - onion_toggle->set_disabled(animlist.size() == 0); - onion_skinning->set_disabled(animlist.size() == 0); pin->set_disabled(player == nullptr); if (!player) { AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); - EditorNode::get_singleton()->update_keying(); return; } + List<StringName> libraries; + player->get_animation_library_list(&libraries); + int active_idx = -1; - for (const StringName &E : animlist) { - Ref<Texture2D> icon; - if (E == player->get_autoplay()) { - if (E == SceneStringNames::get_singleton()->RESET) { - icon = autoplay_reset_icon; - } else { - icon = autoplay_icon; - } - } else if (E == SceneStringNames::get_singleton()->RESET) { - icon = reset_icon; + bool no_anims_found = true; + bool foreign_global_anim_lib = false; + + for (const StringName &K : libraries) { + if (K != StringName()) { + animation->add_separator(K); } - animation->add_icon_item(icon, E); - if (player->get_assigned_animation() == E) { - active_idx = animation->get_item_count() - 1; + // Check if the global library is foreign since we want to disable options for adding/remove/renaming animations if it is. + Ref<AnimationLibrary> anim_library = player->get_animation_library(K); + if (K == "") { + foreign_global_anim_lib = EditorNode::get_singleton()->is_resource_read_only(anim_library); + } + + List<StringName> animlist; + anim_library->get_animation_list(&animlist); + + for (const StringName &E : animlist) { + String path = K; + if (path != "") { + path += "/"; + } + path += E; + animation->add_item(path); + if (player->get_assigned_animation() == path) { + active_idx = animation->get_selectable_item(true); + } + no_anims_found = false; } } +#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), foreign_global_anim_lib) + + ITEM_CHECK_DISABLED(TOOL_NEW_ANIM); + +#undef ITEM_CHECK_DISABLED + +#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), no_anims_found || foreign_global_anim_lib) + + ITEM_CHECK_DISABLED(TOOL_DUPLICATE_ANIM); + ITEM_CHECK_DISABLED(TOOL_RENAME_ANIM); + ITEM_CHECK_DISABLED(TOOL_EDIT_TRANSITIONS); + ITEM_CHECK_DISABLED(TOOL_REMOVE_ANIM); + ITEM_CHECK_DISABLED(TOOL_EDIT_RESOURCE); + +#undef ITEM_CHECK_DISABLED + + stop->set_disabled(no_anims_found); + play->set_disabled(no_anims_found); + play_bw->set_disabled(no_anims_found); + play_bw_from->set_disabled(no_anims_found); + play_from->set_disabled(no_anims_found); + frame->set_editable(!no_anims_found); + animation->set_disabled(no_anims_found); + autoplay->set_disabled(no_anims_found); + onion_toggle->set_disabled(no_anims_found); + onion_skinning->set_disabled(no_anims_found); + + if (hack_disable_onion_skinning) { + onion_toggle->set_disabled(true); + onion_skinning->set_disabled(true); + } + + _update_animation_list_icons(); updating = false; if (active_idx != -1) { animation->select(active_idx); autoplay->set_pressed(animation->get_item_text(active_idx) == player->get_autoplay()); _animation_selected(active_idx); - - } else if (animation->get_item_count() > 0) { - animation->select(0); - autoplay->set_pressed(animation->get_item_text(0) == player->get_autoplay()); - _animation_selected(0); + } else if (animation->has_selectable_items()) { + int item = animation->get_selectable_item(); + animation->select(item); + autoplay->set_pressed(animation->get_item_text(item) == player->get_autoplay()); + _animation_selected(item); } else { _animation_selected(0); } - if (animation->get_item_count()) { + if (!no_anims_found) { String current = animation->get_item_text(animation->get_selected()); Ref<Animation> anim = player->get_animation(current); - track_editor->set_animation(anim); + + bool animation_library_is_foreign = EditorNode::get_singleton()->is_resource_read_only(anim); + + track_editor->set_animation(anim, animation_library_is_foreign); Node *root = player->get_node(player->get_root()); if (root) { track_editor->set_root(root); @@ -879,17 +943,89 @@ void AnimationPlayerEditor::_update_player() { _update_animation(); } +void AnimationPlayerEditor::_update_animation_list_icons() { + for (int i = 0; i < animation->get_item_count(); i++) { + String anim_name = animation->get_item_text(i); + if (animation->is_item_disabled(i) || animation->is_item_separator(i)) { + continue; + } + + Ref<Texture2D> icon; + if (anim_name == player->get_autoplay()) { + if (anim_name == SceneStringNames::get_singleton()->RESET) { + icon = autoplay_reset_icon; + } else { + icon = autoplay_icon; + } + } else if (anim_name == SceneStringNames::get_singleton()->RESET) { + icon = reset_icon; + } + + animation->set_item_icon(i, icon); + } +} + +void AnimationPlayerEditor::_update_name_dialog_library_dropdown() { + StringName current_library_name; + if (animation->has_selectable_items()) { + String current_animation_name = animation->get_item_text(animation->get_selected()); + Ref<Animation> current_animation = player->get_animation(current_animation_name); + if (current_animation.is_valid()) { + current_library_name = player->find_animation_library(current_animation); + } + } + + List<StringName> libraries; + player->get_animation_library_list(&libraries); + library->clear(); + + // When [Global] isn't present, but other libraries are, add option of creating [Global]. + int index_offset = 0; + if (!player->has_animation_library(StringName())) { + library->add_item(String(TTR("[Global] (create)"))); + library->set_item_metadata(0, ""); + index_offset = 1; + } + + int current_lib_id = index_offset; // Don't default to [Global] if it doesn't exist yet. + for (int i = 0; i < libraries.size(); i++) { + StringName library_name = libraries[i]; + library->add_item((library_name == StringName()) ? String(TTR("[Global]")) : String(library_name)); + library->set_item_metadata(i + index_offset, String(library_name)); + // Default to duplicating into same library. + if (library_name == current_library_name) { + current_lib_id = i + index_offset; + } + } + + if (library->get_item_count() > 1) { + library->select(current_lib_id); + library->show(); + } else { + library->hide(); + } +} + void AnimationPlayerEditor::edit(AnimationPlayer *p_player) { if (player && pin->is_pressed()) { return; // Ignore, pinned. } + + if (player) { + if (player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) { + player->disconnect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated)); + } + } player = p_player; if (player) { + if (!player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) { + player->connect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated)); + } _update_player(); if (onion.enabled) { - if (animation->get_item_count() > 0) { + if (animation->has_selectable_items()) { _start_onion_skinning(); } else { _stop_onion_skinning(); @@ -904,6 +1040,8 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) { track_editor->show_select_node_warning(true); } + + library_editor->set_animation_player(player); } void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay) { @@ -957,7 +1095,7 @@ void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay) } void AnimationPlayerEditor::_animation_duplicate() { - if (!animation->get_item_count()) { + if (!animation->has_selectable_items()) { return; } @@ -967,37 +1105,55 @@ void AnimationPlayerEditor::_animation_duplicate() { return; } - Ref<Animation> new_anim = memnew(Animation); - List<PropertyInfo> plist; - anim->get_property_list(&plist); - for (const PropertyInfo &E : plist) { - if (E.usage & PROPERTY_USAGE_STORAGE) { - new_anim->set(E.name, anim->get(E.name)); + int count = 2; + String new_name = current; + PackedStringArray split = new_name.split("_"); + int last_index = split.size() - 1; + if (last_index > 0 && split[last_index].is_valid_int() && split[last_index].to_int() >= 0) { + count = split[last_index].to_int(); + split.remove_at(last_index); + new_name = String("_").join(split); + } + while (true) { + String attempt = new_name; + attempt += vformat("_%d", count); + if (player->has_animation(attempt)) { + count++; + continue; } + new_name = attempt; + break; } - new_anim->set_path(""); - String new_name = current; - while (player->has_animation(new_name)) { - new_name = new_name + " (copy)"; + if (new_name.contains("/")) { + // Discard library prefix. + new_name = new_name.get_slice("/", 1); } - new_anim->set_name(new_name); - undo_redo->create_action(TTR("Duplicate Animation")); - undo_redo->add_do_method(player, "add_animation", new_name, new_anim); - undo_redo->add_undo_method(player, "remove_animation", new_name); - undo_redo->add_do_method(player, "animation_set_next", new_name, player->animation_get_next(current)); - undo_redo->add_do_method(this, "_animation_player_changed", player); - undo_redo->add_undo_method(this, "_animation_player_changed", player); - undo_redo->commit_action(); + _update_name_dialog_library_dropdown(); - for (int i = 0; i < animation->get_item_count(); i++) { - if (animation->get_item_text(i) == new_name) { - animation->select(i); - _animation_selected(i); - return; + name_dialog_op = TOOL_DUPLICATE_ANIM; + name_dialog->set_title(TTR("Duplicate Animation")); + name_title->set_text(TTR("Duplicated Animation Name:")); + name->set_text(new_name); + name_dialog->popup_centered(Size2(300, 90)); + name->select_all(); + name->grab_focus(); +} + +Ref<Animation> AnimationPlayerEditor::_animation_clone(Ref<Animation> p_anim) { + Ref<Animation> new_anim = memnew(Animation); + List<PropertyInfo> plist; + p_anim->get_property_list(&plist); + + for (const PropertyInfo &E : plist) { + if (E.usage & PROPERTY_USAGE_STORAGE) { + new_anim->set(E.name, p_anim->get(E.name)); } } + new_anim->set_path(""); + + return new_anim; } void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool p_timeline_only) { @@ -1027,7 +1183,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool player->seek_delta(pos, pos - cpos); } else { - player->stop(true); + player->stop(); player->seek(pos, true); } } @@ -1041,9 +1197,16 @@ void AnimationPlayerEditor::_animation_player_changed(Object *p_pl) { if (blend_editor.dialog->is_visible()) { _animation_blend(); // Update. } + if (library_editor->is_visible()) { + library_editor->update_tree(); + } } } +void AnimationPlayerEditor::_animation_libraries_updated() { + _animation_player_changed(player); +} + void AnimationPlayerEditor::_list_changed() { if (is_visible_in_tree()) { _update_player(); @@ -1080,10 +1243,7 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag, } void AnimationPlayerEditor::_animation_tool_menu(int p_option) { - String current; - if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) { - current = animation->get_item_text(animation->get_selected()); - } + String current = _get_current(); Ref<Animation> anim; if (!current.is_empty()) { @@ -1094,24 +1254,13 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) { case TOOL_NEW_ANIM: { _animation_new(); } break; - case TOOL_LOAD_ANIM: { - _animation_load(); - } break; - case TOOL_SAVE_ANIM: { - if (anim.is_valid()) { - _animation_save(anim); - } - } break; - case TOOL_SAVE_AS_ANIM: { - if (anim.is_valid()) { - _animation_save_as(anim); - } + case TOOL_ANIM_LIBRARY: { + library_editor->set_animation_player(player); + library_editor->show_dialog(); } break; case TOOL_DUPLICATE_ANIM: { _animation_duplicate(); - - [[fallthrough]]; // Allow immediate rename after animation is duplicated - } + } break; case TOOL_RENAME_ANIM: { _animation_rename(); } break; @@ -1121,56 +1270,10 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) { case TOOL_REMOVE_ANIM: { _animation_remove(); } break; - case TOOL_COPY_ANIM: { - if (!animation->get_item_count()) { - error_dialog->set_text(TTR("No animation to copy!")); - error_dialog->popup_centered(); - return; - } - - String current2 = animation->get_item_text(animation->get_selected()); - Ref<Animation> anim2 = player->get_animation(current2); - EditorSettings::get_singleton()->set_resource_clipboard(anim2); - } break; - case TOOL_PASTE_ANIM: { - Ref<Animation> anim2 = EditorSettings::get_singleton()->get_resource_clipboard(); - if (!anim2.is_valid()) { - error_dialog->set_text(TTR("No animation resource on clipboard!")); - error_dialog->popup_centered(); - return; - } - - String name = anim2->get_name(); - if (name.is_empty()) { - name = TTR("Pasted Animation"); - } - - int idx = 1; - String base = name; - while (player->has_animation(name)) { - idx++; - name = base + " " + itos(idx); - } - - undo_redo->create_action(TTR("Paste Animation")); - undo_redo->add_do_method(player, "add_animation", name, anim2); - undo_redo->add_undo_method(player, "remove_animation", name); - undo_redo->add_do_method(this, "_animation_player_changed", player); - undo_redo->add_undo_method(this, "_animation_player_changed", player); - undo_redo->commit_action(); - - _select_anim_by_name(name); - } break; case TOOL_EDIT_RESOURCE: { - if (!animation->get_item_count()) { - error_dialog->set_text(TTR("No animation to edit!")); - error_dialog->popup_centered(); - return; + if (anim.is_valid()) { + EditorNode::get_singleton()->edit_resource(anim); } - - String current2 = animation->get_item_text(animation->get_selected()); - Ref<Animation> anim2 = player->get_animation(current2); - editor->edit_resource(anim2); } break; } } @@ -1224,7 +1327,7 @@ void AnimationPlayerEditor::_onion_skinning_menu(int p_option) { } } -void AnimationPlayerEditor::unhandled_key_input(const Ref<InputEvent> &p_ev) { +void AnimationPlayerEditor::shortcut_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventKey> k = p_ev; @@ -1257,7 +1360,7 @@ void AnimationPlayerEditor::unhandled_key_input(const Ref<InputEvent> &p_ev) { } void AnimationPlayerEditor::_editor_visibility_changed() { - if (is_visible() && animation->get_item_count() > 0) { + if (is_visible() && animation->has_selectable_items()) { _start_onion_skinning(); } } @@ -1309,8 +1412,8 @@ void AnimationPlayerEditor::_free_onion_layers() { void AnimationPlayerEditor::_prepare_onion_layers_1() { // This would be called per viewport and we want to act once only. - int64_t frame = get_tree()->get_frame(); - if (frame == onion.last_frame) { + int64_t cur_frame = get_tree()->get_frame(); + if (cur_frame == onion.last_frame) { return; } @@ -1319,7 +1422,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_1() { return; } - onion.last_frame = frame; + onion.last_frame = cur_frame; // Refresh viewports with no onion layers overlaid. onion.can_overlay = false; @@ -1407,26 +1510,26 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { // Render every past/future step with the capture shader. RS::get_singleton()->canvas_item_set_material(onion.capture.canvas_item, onion.capture.material->get_rid()); - onion.capture.material->set_shader_param("bkg_color", GLOBAL_GET("rendering/environment/defaults/default_clear_color")); - onion.capture.material->set_shader_param("differences_only", onion.differences_only); - onion.capture.material->set_shader_param("present", onion.differences_only ? RS::get_singleton()->viewport_get_texture(present_rid) : RID()); + onion.capture.material->set_shader_parameter("bkg_color", GLOBAL_GET("rendering/environment/defaults/default_clear_color")); + onion.capture.material->set_shader_parameter("differences_only", onion.differences_only); + onion.capture.material->set_shader_parameter("present", onion.differences_only ? RS::get_singleton()->viewport_get_texture(present_rid) : RID()); int step_off_a = onion.past ? -onion.steps : 0; int step_off_b = onion.future ? onion.steps : 0; int cidx = 0; - onion.capture.material->set_shader_param("dir_color", onion.force_white_modulate ? Color(1, 1, 1) : Color(EDITOR_GET("editors/animation/onion_layers_past_color"))); + onion.capture.material->set_shader_parameter("dir_color", onion.force_white_modulate ? Color(1, 1, 1) : Color(EDITOR_GET("editors/animation/onion_layers_past_color"))); for (int step_off = step_off_a; step_off <= step_off_b; step_off++) { if (step_off == 0) { // Skip present step and switch to the color of future. if (!onion.force_white_modulate) { - onion.capture.material->set_shader_param("dir_color", EDITOR_GET("editors/animation/onion_layers_future_color")); + onion.capture.material->set_shader_parameter("dir_color", EDITOR_GET("editors/animation/onion_layers_future_color")); } continue; } float pos = cpos + step_off * anim->get_step(); - bool valid = anim->get_loop_mode() != Animation::LoopMode::LOOP_NONE || (pos >= 0 && pos <= anim->get_length()); + bool valid = anim->get_loop_mode() != Animation::LOOP_NONE || (pos >= 0 && pos <= anim->get_length()); onion.captures_valid.write[cidx] = valid; if (valid) { player->seek(pos, true); @@ -1487,18 +1590,65 @@ void AnimationPlayerEditor::_stop_onion_skinning() { } void AnimationPlayerEditor::_pin_pressed() { - EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->update_tree(); + SceneTreeDock::get_singleton()->get_tree_editor()->update_tree(); +} + +bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) { + bool is_valid = true; + if (!p_anim.is_valid()) { + return true; // There is a problem outside of the animation track. + } + int len = p_anim->get_track_count(); + for (int i = 0; i < len; i++) { + Animation::TrackType ttype = p_anim->track_get_type(i); + if (ttype == Animation::TYPE_ROTATION_3D) { + int key_len = p_anim->track_get_key_count(i); + for (int j = 0; j < key_len; j++) { + Quaternion q; + p_anim->rotation_track_get_key(i, j, &q); + ERR_BREAK_EDMSG(!q.is_normalized(), "AnimationPlayer: '" + player->get_name() + "', Animation: '" + player->get_current_animation() + "', rotation track: '" + p_anim->track_get_path(i) + "' contains unnormalized Quaternion key."); + } + } else if (ttype == Animation::TYPE_VALUE) { + int key_len = p_anim->track_get_key_count(i); + if (key_len == 0) { + continue; + } + switch (p_anim->track_get_key_value(i, 0).get_type()) { + case Variant::QUATERNION: { + for (int j = 0; j < key_len; j++) { + Quaternion q = Quaternion(p_anim->track_get_key_value(i, j)); + if (!q.is_normalized()) { + is_valid = false; + ERR_BREAK_EDMSG(true, "AnimationPlayer: '" + player->get_name() + "', Animation: '" + player->get_current_animation() + "', value track: '" + p_anim->track_get_path(i) + "' contains unnormalized Quaternion key."); + } + } + } break; + case Variant::TRANSFORM3D: { + for (int j = 0; j < key_len; j++) { + Transform3D t = Transform3D(p_anim->track_get_key_value(i, j)); + if (!t.basis.orthonormalized().is_rotation()) { + is_valid = false; + ERR_BREAK_EDMSG(true, "AnimationPlayer: '" + player->get_name() + "', Animation: '" + player->get_current_animation() + "', value track: '" + p_anim->track_get_path(i) + "' contains corrupted basis (some axes are too close other axis or scaled by zero) Transform3D key."); + } + } + } break; + default: { + } break; + } + } + } + return is_valid; } void AnimationPlayerEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_animation_new"), &AnimationPlayerEditor::_animation_new); ClassDB::bind_method(D_METHOD("_animation_rename"), &AnimationPlayerEditor::_animation_rename); - ClassDB::bind_method(D_METHOD("_animation_load"), &AnimationPlayerEditor::_animation_load); ClassDB::bind_method(D_METHOD("_animation_remove"), &AnimationPlayerEditor::_animation_remove); ClassDB::bind_method(D_METHOD("_animation_blend"), &AnimationPlayerEditor::_animation_blend); ClassDB::bind_method(D_METHOD("_animation_edit"), &AnimationPlayerEditor::_animation_edit); ClassDB::bind_method(D_METHOD("_animation_resource_edit"), &AnimationPlayerEditor::_animation_resource_edit); ClassDB::bind_method(D_METHOD("_animation_player_changed"), &AnimationPlayerEditor::_animation_player_changed); + ClassDB::bind_method(D_METHOD("_animation_libraries_updated"), &AnimationPlayerEditor::_animation_libraries_updated); ClassDB::bind_method(D_METHOD("_list_changed"), &AnimationPlayerEditor::_list_changed); ClassDB::bind_method(D_METHOD("_animation_duplicate"), &AnimationPlayerEditor::_animation_duplicate); @@ -1506,6 +1656,8 @@ void AnimationPlayerEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_prepare_onion_layers_2"), &AnimationPlayerEditor::_prepare_onion_layers_2); ClassDB::bind_method(D_METHOD("_start_onion_skinning"), &AnimationPlayerEditor::_start_onion_skinning); ClassDB::bind_method(D_METHOD("_stop_onion_skinning"), &AnimationPlayerEditor::_stop_onion_skinning); + + ADD_SIGNAL(MethodInfo("animation_selected", PropertyInfo(Variant::STRING, "name"))); } AnimationPlayerEditor *AnimationPlayerEditor::singleton = nullptr; @@ -1514,8 +1666,7 @@ AnimationPlayer *AnimationPlayerEditor::get_player() const { return player; } -AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlayerEditorPlugin *p_plugin) { - editor = p_editor; +AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plugin) { plugin = p_plugin; singleton = this; @@ -1530,36 +1681,35 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay play_bw_from = memnew(Button); play_bw_from->set_flat(true); - play_bw_from->set_tooltip(TTR("Play selected animation backwards from current pos. (A)")); + play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)")); hb->add_child(play_bw_from); play_bw = memnew(Button); play_bw->set_flat(true); - play_bw->set_tooltip(TTR("Play selected animation backwards from end. (Shift+A)")); + play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)")); hb->add_child(play_bw); stop = memnew(Button); stop->set_flat(true); - stop->set_toggle_mode(true); hb->add_child(stop); - stop->set_tooltip(TTR("Stop animation playback. (S)")); + stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)")); play = memnew(Button); play->set_flat(true); - play->set_tooltip(TTR("Play selected animation from start. (Shift+D)")); + play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)")); hb->add_child(play); play_from = memnew(Button); play_from->set_flat(true); - play_from->set_tooltip(TTR("Play selected animation from current pos. (D)")); + play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)")); hb->add_child(play_from); frame = memnew(SpinBox); hb->add_child(frame); - frame->set_custom_minimum_size(Size2(60, 0)); + frame->set_custom_minimum_size(Size2(80, 0) * EDSCALE); frame->set_stretch_ratio(2); frame->set_step(0.0001); - frame->set_tooltip(TTR("Animation position (in seconds).")); + frame->set_tooltip_text(TTR("Animation position (in seconds).")); hb->add_child(memnew(VSeparator)); @@ -1567,7 +1717,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay hb->add_child(scale); scale->set_h_size_flags(SIZE_EXPAND_FILL); scale->set_stretch_ratio(1); - scale->set_tooltip(TTR("Scale animation playback globally for the node.")); + scale->set_tooltip_text(TTR("Scale animation playback globally for the node.")); scale->hide(); delete_dialog = memnew(ConfirmationDialog); @@ -1577,36 +1727,32 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay tool_anim = memnew(MenuButton); tool_anim->set_shortcut_context(this); tool_anim->set_flat(false); - tool_anim->set_tooltip(TTR("Animation Tools")); + tool_anim->set_tooltip_text(TTR("Animation Tools")); tool_anim->set_text(TTR("Animation")); tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTR("New")), TOOL_NEW_ANIM); tool_anim->get_popup()->add_separator(); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation", TTR("Load")), TOOL_LOAD_ANIM); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_animation", TTR("Save")), TOOL_SAVE_ANIM); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_as_animation", TTR("Save As...")), TOOL_SAVE_AS_ANIM); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/animation_libraries", TTR("Manage Animations...")), TOOL_ANIM_LIBRARY); tool_anim->get_popup()->add_separator(); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/copy_animation", TTR("Copy")), TOOL_COPY_ANIM); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/paste_animation", TTR("Paste")), TOOL_PASTE_ANIM); - tool_anim->get_popup()->add_separator(); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate")), TOOL_DUPLICATE_ANIM); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate...")), TOOL_DUPLICATE_ANIM); tool_anim->get_popup()->add_separator(); tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/rename_animation", TTR("Rename...")), TOOL_RENAME_ANIM); tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/edit_transitions", TTR("Edit Transitions...")), TOOL_EDIT_TRANSITIONS); tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation_in_inspector", TTR("Open in Inspector")), TOOL_EDIT_RESOURCE); tool_anim->get_popup()->add_separator(); tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove")), TOOL_REMOVE_ANIM); + tool_anim->set_disabled(true); hb->add_child(tool_anim); animation = memnew(OptionButton); hb->add_child(animation); animation->set_h_size_flags(SIZE_EXPAND_FILL); - animation->set_tooltip(TTR("Display list of animations in player.")); + animation->set_tooltip_text(TTR("Display list of animations in player.")); animation->set_clip_text(true); autoplay = memnew(Button); autoplay->set_flat(true); hb->add_child(autoplay); - autoplay->set_tooltip(TTR("Autoplay on Load")); + autoplay->set_tooltip_text(TTR("Autoplay on Load")); hb->add_child(memnew(VSeparator)); @@ -1619,19 +1765,21 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay onion_toggle = memnew(Button); onion_toggle->set_flat(true); onion_toggle->set_toggle_mode(true); - onion_toggle->set_tooltip(TTR("Enable Onion Skinning")); - onion_toggle->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu), varray(ONION_SKINNING_ENABLE)); + onion_toggle->set_tooltip_text(TTR("Enable Onion Skinning")); + onion_toggle->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu).bind(ONION_SKINNING_ENABLE)); hb->add_child(onion_toggle); onion_skinning = memnew(MenuButton); - onion_skinning->set_tooltip(TTR("Onion Skinning Options")); + onion_skinning->set_tooltip_text(TTR("Onion Skinning Options")); onion_skinning->get_popup()->add_separator(TTR("Directions")); + // TRANSLATORS: Opposite of "Future", refers to a direction in animation onion skinning. onion_skinning->get_popup()->add_check_item(TTR("Past"), ONION_SKINNING_PAST); - onion_skinning->get_popup()->set_item_checked(onion_skinning->get_popup()->get_item_count() - 1, true); + onion_skinning->get_popup()->set_item_checked(-1, true); + // TRANSLATORS: Opposite of "Past", refers to a direction in animation onion skinning. onion_skinning->get_popup()->add_check_item(TTR("Future"), ONION_SKINNING_FUTURE); onion_skinning->get_popup()->add_separator(TTR("Depth")); onion_skinning->get_popup()->add_radio_check_item(TTR("1 step"), ONION_SKINNING_1_STEP); - onion_skinning->get_popup()->set_item_checked(onion_skinning->get_popup()->get_item_count() - 1, true); + onion_skinning->get_popup()->set_item_checked(-1, true); onion_skinning->get_popup()->add_radio_check_item(TTR("2 steps"), ONION_SKINNING_2_STEPS); onion_skinning->get_popup()->add_radio_check_item(TTR("3 steps"), ONION_SKINNING_3_STEPS); onion_skinning->get_popup()->add_separator(); @@ -1640,12 +1788,22 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay onion_skinning->get_popup()->add_check_item(TTR("Include Gizmos (3D)"), ONION_SKINNING_INCLUDE_GIZMOS); hb->add_child(onion_skinning); + // FIXME: Onion skinning disabled for now as it's broken and triggers fast + // flickering red/blue modulation (GH-53870). + if (hack_disable_onion_skinning) { + onion_toggle->set_disabled(true); + onion_toggle->set_tooltip_text(TTR("Onion Skinning temporarily disabled due to rendering bug.")); + + onion_skinning->set_disabled(true); + onion_skinning->set_tooltip_text(TTR("Onion Skinning temporarily disabled due to rendering bug.")); + } + hb->add_child(memnew(VSeparator)); pin = memnew(Button); pin->set_flat(true); pin->set_toggle_mode(true); - pin->set_tooltip(TTR("Pin AnimationPlayer")); + pin->set_tooltip_text(TTR("Pin AnimationPlayer")); hb->add_child(pin); pin->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_pin_pressed)); @@ -1662,12 +1820,18 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay name_title = memnew(Label(TTR("Animation Name:"))); vb->add_child(name_title); + HBoxContainer *name_hb = memnew(HBoxContainer); name = memnew(LineEdit); - vb->add_child(name); + name_hb->add_child(name); + name->set_h_size_flags(SIZE_EXPAND_FILL); + library = memnew(OptionButton); + name_hb->add_child(library); + library->hide(); + vb->add_child(name_hb); name_dialog->register_text_enter(name); error_dialog = memnew(ConfirmationDialog); - error_dialog->get_ok_button()->set_text(TTR("Close")); + error_dialog->set_ok_button_text(TTR("Close")); error_dialog->set_title(TTR("Error!")); add_child(error_dialog); @@ -1675,7 +1839,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay blend_editor.dialog = memnew(AcceptDialog); add_child(blend_editor.dialog); - blend_editor.dialog->get_ok_button()->set_text(TTR("Close")); + blend_editor.dialog->set_ok_button_text(TTR("Close")); blend_editor.dialog->set_hide_on_ok(true); VBoxContainer *blend_vb = memnew(VBoxContainer); blend_editor.dialog->add_child(blend_vb); @@ -1699,16 +1863,13 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay animation->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_animation_selected)); - file->connect("file_selected", callable_mp(this, &AnimationPlayerEditor::_save_animation)); - file->connect("files_selected", callable_mp(this, &AnimationPlayerEditor::_load_animations)); - frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed), make_binds(true, false)); + frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed).bind(true, false)); scale->connect("text_submitted", callable_mp(this, &AnimationPlayerEditor::_scale_changed)); - renaming = false; last_active = false; timeline_position = 0; - set_process_unhandled_key_input(true); + set_process_shortcut_input(true); add_child(track_editor); track_editor->set_v_size_flags(SIZE_EXPAND_FILL); @@ -1717,6 +1878,10 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay _update_player(); + library_editor = memnew(AnimationLibraryEditor); + add_child(library_editor); + library_editor->connect("update_editor", callable_mp(this, &AnimationPlayerEditor::_animation_player_changed)); + // Onion skinning. track_editor->connect("visibility_changed", callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed)); @@ -1774,13 +1939,42 @@ AnimationPlayerEditor::~AnimationPlayerEditor() { void AnimationPlayerEditorPlugin::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + Node3DEditor::get_singleton()->connect("transform_key_request", callable_mp(this, &AnimationPlayerEditorPlugin::_transform_key_request)); + InspectorDock::get_inspector_singleton()->connect("property_keyed", callable_mp(this, &AnimationPlayerEditorPlugin::_property_keyed)); + anim_editor->get_track_editor()->connect("keying_changed", callable_mp(this, &AnimationPlayerEditorPlugin::_update_keying)); + InspectorDock::get_inspector_singleton()->connect("edited_object_changed", callable_mp(anim_editor->get_track_editor(), &AnimationTrackEditor::update_keying)); set_force_draw_over_forwarding_enabled(); } break; } } +void AnimationPlayerEditorPlugin::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) { + AnimationTrackEditor *te = anim_editor->get_track_editor(); + if (!te || !te->has_keying()) { + return; + } + te->_clear_selection(); + te->insert_value_key(p_keyed, p_value, p_advance); +} + +void AnimationPlayerEditorPlugin::_transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key) { + if (!anim_editor->get_track_editor()->has_keying()) { + return; + } + Node3D *s = Object::cast_to<Node3D>(sp); + if (!s) { + return; + } + anim_editor->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_POSITION_3D, p_key.origin); + anim_editor->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_ROTATION_3D, p_key.basis.get_rotation_quaternion()); + anim_editor->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_SCALE_3D, p_key.basis.get_scale()); +} + +void AnimationPlayerEditorPlugin::_update_keying() { + InspectorDock::get_inspector_singleton()->set_keying(anim_editor->get_track_editor()->has_keying()); +} + void AnimationPlayerEditorPlugin::edit(Object *p_object) { - anim_editor->set_undo_redo(&get_undo_redo()); if (!p_object) { return; } @@ -1793,18 +1987,39 @@ bool AnimationPlayerEditorPlugin::handles(Object *p_object) const { void AnimationPlayerEditorPlugin::make_visible(bool p_visible) { if (p_visible) { - editor->make_bottom_panel_item_visible(anim_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(anim_editor); anim_editor->set_process(true); anim_editor->ensure_visibility(); } } -AnimationPlayerEditorPlugin::AnimationPlayerEditorPlugin(EditorNode *p_node) { - editor = p_node; - anim_editor = memnew(AnimationPlayerEditor(editor, this)); - anim_editor->set_undo_redo(EditorNode::get_undo_redo()); - editor->add_bottom_panel_item(TTR("Animation"), anim_editor); +AnimationPlayerEditorPlugin::AnimationPlayerEditorPlugin() { + anim_editor = memnew(AnimationPlayerEditor(this)); + EditorNode::get_singleton()->add_bottom_panel_item(TTR("Animation"), anim_editor); } AnimationPlayerEditorPlugin::~AnimationPlayerEditorPlugin() { } + +// AnimationTrackKeyEditEditorPlugin + +bool EditorInspectorPluginAnimationTrackKeyEdit::can_handle(Object *p_object) { + return Object::cast_to<AnimationTrackKeyEdit>(p_object) != nullptr; +} + +void EditorInspectorPluginAnimationTrackKeyEdit::parse_begin(Object *p_object) { + AnimationTrackKeyEdit *atk = Object::cast_to<AnimationTrackKeyEdit>(p_object); + ERR_FAIL_COND(!atk); + + atk_editor = memnew(AnimationTrackKeyEditEditor(atk->animation, atk->track, atk->key_ofs, atk->use_fps)); + add_custom_control(atk_editor); +} + +AnimationTrackKeyEditEditorPlugin::AnimationTrackKeyEditEditorPlugin() { + atk_plugin = memnew(EditorInspectorPluginAnimationTrackKeyEdit); + EditorInspector::add_inspector_plugin(atk_plugin); +} + +bool AnimationTrackKeyEditEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("AnimationTrackKeyEdit"); +} diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 4e7ea46c1d..327200506f 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -1,65 +1,61 @@ -/*************************************************************************/ -/* animation_player_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_player_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 ANIMATION_PLAYER_EDITOR_PLUGIN_H #define ANIMATION_PLAYER_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/animation_track_editor.h" #include "editor/editor_plugin.h" +#include "editor/plugins/animation_library_editor.h" #include "scene/animation/animation_player.h" #include "scene/gui/dialogs.h" #include "scene/gui/slider.h" #include "scene/gui/spin_box.h" #include "scene/gui/texture_button.h" +#include "scene/gui/tree.h" -class AnimationTrackEditor; class AnimationPlayerEditorPlugin; class AnimationPlayerEditor : public VBoxContainer { GDCLASS(AnimationPlayerEditor, VBoxContainer); - EditorNode *editor; - AnimationPlayerEditorPlugin *plugin; - AnimationPlayer *player; + AnimationPlayerEditorPlugin *plugin = nullptr; + AnimationPlayer *player = nullptr; enum { TOOL_NEW_ANIM, - TOOL_LOAD_ANIM, - TOOL_SAVE_ANIM, - TOOL_SAVE_AS_ANIM, + TOOL_ANIM_LIBRARY, TOOL_DUPLICATE_ANIM, TOOL_RENAME_ANIM, TOOL_EDIT_TRANSITIONS, TOOL_REMOVE_ANIM, - TOOL_COPY_ANIM, - TOOL_PASTE_ANIM, TOOL_EDIT_RESOURCE }; @@ -87,31 +83,36 @@ class AnimationPlayerEditor : public VBoxContainer { RESOURCE_SAVE }; - OptionButton *animation; - Button *stop; - Button *play; - Button *play_from; - Button *play_bw; - Button *play_bw_from; - Button *autoplay; - - MenuButton *tool_anim; - Button *onion_toggle; - MenuButton *onion_skinning; - Button *pin; - SpinBox *frame; - LineEdit *scale; - LineEdit *name; - Label *name_title; - UndoRedo *undo_redo; + OptionButton *animation = nullptr; + Button *stop = nullptr; + Button *play = nullptr; + Button *play_from = nullptr; + Button *play_bw = nullptr; + Button *play_bw_from = nullptr; + Button *autoplay = nullptr; + + MenuButton *tool_anim = nullptr; + Button *onion_toggle = nullptr; + MenuButton *onion_skinning = nullptr; + Button *pin = nullptr; + SpinBox *frame = nullptr; + LineEdit *scale = nullptr; + LineEdit *name = nullptr; + OptionButton *library = nullptr; + Label *name_title = nullptr; + + Ref<Texture2D> stop_icon; + Ref<Texture2D> pause_icon; Ref<Texture2D> autoplay_icon; Ref<Texture2D> reset_icon; Ref<ImageTexture> autoplay_reset_icon; bool last_active; float timeline_position; - EditorFileDialog *file; - ConfirmationDialog *delete_dialog; + EditorFileDialog *file = nullptr; + ConfirmationDialog *delete_dialog = nullptr; + + AnimationLibraryEditor *library_editor = nullptr; struct BlendEditor { AcceptDialog *dialog = nullptr; @@ -120,16 +121,18 @@ class AnimationPlayerEditor : public VBoxContainer { } blend_editor; - ConfirmationDialog *name_dialog; - ConfirmationDialog *error_dialog; - bool renaming; + ConfirmationDialog *name_dialog = nullptr; + ConfirmationDialog *error_dialog = nullptr; + int name_dialog_op = TOOL_NEW_ANIM; bool updating; bool updating_blends; - AnimationTrackEditor *track_editor; + AnimationTrackEditor *track_editor = nullptr; static AnimationPlayerEditor *singleton; + bool hack_disable_onion_skinning = true; // Temporary hack for GH-53870. + // Onion skinning. struct { // Settings. @@ -161,7 +164,7 @@ class AnimationPlayerEditor : public VBoxContainer { } onion; void _select_anim_by_name(const String &p_anim); - double _get_editor_step() const; + float _get_editor_step() const; void _play_pressed(); void _play_from_pressed(); void _play_bw_pressed(); @@ -172,35 +175,32 @@ class AnimationPlayerEditor : public VBoxContainer { void _animation_new(); void _animation_rename(); void _animation_name_edited(); - void _animation_load(); - - void _animation_save_in_path(const Ref<Resource> &p_resource, const String &p_path); - void _animation_save(const Ref<Resource> &p_resource); - void _animation_save_as(const Ref<Resource> &p_resource); void _animation_remove(); void _animation_remove_confirmed(); void _animation_blend(); void _animation_edit(); void _animation_duplicate(); + Ref<Animation> _animation_clone(const Ref<Animation> p_anim); void _animation_resource_edit(); void _scale_changed(const String &p_scale); - void _save_animation(String p_file); - void _load_animations(Vector<String> p_files); void _seek_value_changed(float p_value, bool p_set = false, bool p_timeline_only = false); void _blend_editor_next_changed(const int p_idx); void _list_changed(); void _update_animation(); void _update_player(); + void _update_animation_list_icons(); + void _update_name_dialog_library_dropdown(); void _blend_edited(); void _animation_player_changed(Object *p_pl); + void _animation_libraries_updated(); void _animation_key_editor_seek(float p_pos, bool p_drag, bool p_timeline_only = false); void _animation_key_editor_anim_len_changed(float p_len); - virtual void unhandled_key_input(const Ref<InputEvent> &p_ev) override; + virtual void shortcut_input(const Ref<InputEvent> &p_ev) override; void _animation_tool_menu(int p_option); void _onion_skinning_menu(int p_option); @@ -214,7 +214,10 @@ class AnimationPlayerEditor : public VBoxContainer { void _start_onion_skinning(); void _stop_onion_skinning(); + bool _validate_tracks(const Ref<Animation> p_anim); + void _pin_pressed(); + String _get_current() const; ~AnimationPlayerEditor(); @@ -236,22 +239,24 @@ public: void ensure_visibility(); - void set_undo_redo(UndoRedo *p_undo_redo) { undo_redo = p_undo_redo; } void edit(AnimationPlayer *p_player); void forward_force_draw_over_viewport(Control *p_overlay); - AnimationPlayerEditor(EditorNode *p_editor, AnimationPlayerEditorPlugin *p_plugin); + AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plugin); }; class AnimationPlayerEditorPlugin : public EditorPlugin { GDCLASS(AnimationPlayerEditorPlugin, EditorPlugin); - AnimationPlayerEditor *anim_editor; - EditorNode *editor; + AnimationPlayerEditor *anim_editor = nullptr; protected: void _notification(int p_what); + void _property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance); + void _transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key); + void _update_keying(); + public: virtual Dictionary get_state() const override { return anim_editor->get_state(); } virtual void set_state(const Dictionary &p_state) override { anim_editor->set_state(p_state); } @@ -263,10 +268,36 @@ public: virtual void make_visible(bool p_visible) override; virtual void forward_canvas_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_force_draw_over_viewport(p_overlay); } - virtual void forward_spatial_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_force_draw_over_viewport(p_overlay); } + virtual void forward_3d_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_force_draw_over_viewport(p_overlay); } - AnimationPlayerEditorPlugin(EditorNode *p_node); + AnimationPlayerEditorPlugin(); ~AnimationPlayerEditorPlugin(); }; +// AnimationTrackKeyEditEditorPlugin + +class EditorInspectorPluginAnimationTrackKeyEdit : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginAnimationTrackKeyEdit, EditorInspectorPlugin); + + AnimationTrackKeyEditEditor *atk_editor = nullptr; + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; +}; + +class AnimationTrackKeyEditEditorPlugin : public EditorPlugin { + GDCLASS(AnimationTrackKeyEditEditorPlugin, EditorPlugin); + + EditorInspectorPluginAnimationTrackKeyEdit *atk_plugin = nullptr; + +public: + bool has_main_screen() const override { return false; } + virtual bool handles(Object *p_object) const override; + + virtual String get_name() const override { return "AnimationTrackKeyEdit"; } + + AnimationTrackKeyEditEditorPlugin(); +}; + #endif // ANIMATION_PLAYER_EDITOR_PLUGIN_H diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index 94990636da..9632670658 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* animation_state_machine_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_state_machine_editor.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 "animation_state_machine_editor.h" @@ -35,11 +35,20 @@ #include "core/io/resource_loader.h" #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "scene/animation/animation_blend_tree.h" #include "scene/animation/animation_player.h" #include "scene/gui/menu_button.h" +#include "scene/gui/option_button.h" #include "scene/gui/panel.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/separator.h" +#include "scene/gui/tree.h" +#include "scene/main/viewport.h" #include "scene/main/window.h" bool AnimationNodeStateMachineEditor::can_edit(const Ref<AnimationNode> &p_node) { @@ -50,88 +59,75 @@ bool AnimationNodeStateMachineEditor::can_edit(const Ref<AnimationNode> &p_node) void AnimationNodeStateMachineEditor::edit(const Ref<AnimationNode> &p_node) { state_machine = p_node; + read_only = false; + if (state_machine.is_valid()) { + read_only = EditorNode::get_singleton()->is_resource_read_only(state_machine); + selected_transition_from = StringName(); selected_transition_to = StringName(); + selected_transition_index = -1; + selected_multi_transition = TransitionLine(); selected_node = StringName(); + selected_nodes.clear(); _update_mode(); _update_graph(); } + + tool_create->set_disabled(read_only); + tool_connect->set_disabled(read_only); } void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEvent> &p_event) { - Ref<AnimationNodeStateMachinePlayback> playback = AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } + + Ref<AnimationNodeStateMachinePlayback> playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); if (playback.is_null()) { return; } Ref<InputEventKey> k = p_event; if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && !k->is_echo()) { - if (selected_node != StringName() || selected_transition_to != StringName() || selected_transition_from != StringName()) { - _erase_selected(); + if (selected_node != StringName() || !selected_nodes.is_empty() || selected_transition_to != StringName() || selected_transition_from != StringName()) { + if (!read_only) { + _erase_selected(); + } accept_event(); } } - Ref<InputEventMouseButton> mb = p_event; + // Group selected nodes on a state machine + if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->is_ctrl_pressed() && !k->is_shift_pressed() && k->get_keycode() == Key::G && !k->is_echo()) { + _group_selected_nodes(); + } - //Add new node - if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) || (tool_create->is_pressed() && mb->get_button_index() == MouseButton::LEFT))) { - menu->clear(); - animations_menu->clear(); - animations_to_add.clear(); - List<StringName> classes; - classes.sort_custom<StringName::AlphCompare>(); - - ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); - menu->add_submenu_item(TTR("Add Animation"), "animations"); - - AnimationTree *gp = AnimationTreeEditor::get_singleton()->get_tree(); - ERR_FAIL_COND(!gp); - if (gp && gp->has_node(gp->get_animation_player())) { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player())); - if (ap) { - List<StringName> names; - ap->get_animation_list(&names); - for (const StringName &E : names) { - animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E); - animations_to_add.push_back(E); - } - } - } + // Ungroup state machine + if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->is_ctrl_pressed() && k->is_shift_pressed() && k->get_keycode() == Key::G && !k->is_echo()) { + _ungroup_selected_nodes(); + } - for (const StringName &E : classes) { - String name = String(E).replace_first("AnimationNode", ""); - if (name == "Animation") { - continue; // nope - } - int idx = menu->get_item_count(); - menu->add_item(vformat("Add %s", name), idx); - menu->set_item_metadata(idx, E); - } - Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard(); + Ref<InputEventMouseButton> mb = p_event; - if (clipb.is_valid()) { - menu->add_separator(); - menu->add_item(TTR("Paste"), MENU_PASTE); + // Add new node + if (!read_only) { + if (mb.is_valid() && mb->is_pressed() && !box_selecting && !connecting && ((tool_select->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) || (tool_create->is_pressed() && mb->get_button_index() == MouseButton::LEFT))) { + connecting_from = StringName(); + _open_menu(mb->get_position()); } - menu->add_separator(); - menu->add_item(TTR("Load..."), MENU_LOAD_FILE); - - menu->set_position(state_machine_draw->get_screen_position() + mb->get_position()); - menu->reset_size(); - menu->popup(); - add_node_pos = mb->get_position() / EDSCALE + state_machine->get_graph_offset(); } - // select node or push a field inside - if (mb.is_valid() && !mb->is_shift_pressed() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + // Select node or push a field inside + if (mb.is_valid() && !mb->is_shift_pressed() && !mb->is_ctrl_pressed() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { selected_transition_from = StringName(); selected_transition_to = StringName(); + selected_transition_index = -1; + selected_multi_transition = TransitionLine(); selected_node = StringName(); for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order - if (node_rects[i].play.has_point(mb->get_position())) { //edit name if (play_mode->get_selected() == 1 || !playback->is_playing()) { //start @@ -140,27 +136,28 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv //travel playback->travel(node_rects[i].node_name); } - state_machine_draw->update(); + state_machine_draw->queue_redraw(); return; } - if (node_rects[i].name.has_point(mb->get_position())) { //edit name - - Ref<StyleBox> line_sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")); + if (!read_only) { + if (node_rects[i].name.has_point(mb->get_position()) && state_machine->can_edit_node(node_rects[i].node_name)) { // edit name + Ref<StyleBox> line_sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")); - Rect2 edit_rect = node_rects[i].name; - edit_rect.position -= line_sb->get_offset(); - edit_rect.size += line_sb->get_minimum_size(); + Rect2 edit_rect = node_rects[i].name; + edit_rect.position -= line_sb->get_offset(); + edit_rect.size += line_sb->get_minimum_size(); - name_edit_popup->set_position(state_machine_draw->get_screen_position() + edit_rect.position); - name_edit_popup->set_size(edit_rect.size); - name_edit->set_text(node_rects[i].node_name); - name_edit_popup->popup(); - name_edit->grab_focus(); - name_edit->select_all(); + name_edit_popup->set_position(state_machine_draw->get_screen_position() + edit_rect.position); + name_edit_popup->set_size(edit_rect.size); + name_edit->set_text(node_rects[i].node_name); + name_edit_popup->popup(); + name_edit->grab_focus(); + name_edit->select_all(); - prev_name = node_rects[i].node_name; - return; + prev_name = node_rects[i].node_name; + return; + } } if (node_rects[i].edit.has_point(mb->get_position())) { //edit name @@ -171,9 +168,15 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected selected_node = node_rects[i].node_name; + if (!selected_nodes.has(selected_node)) { + selected_nodes.clear(); + } + + selected_nodes.insert(selected_node); + Ref<AnimationNode> anode = state_machine->get_node(selected_node); EditorNode::get_singleton()->push_item(anode.ptr(), "", true); - state_machine_draw->update(); + state_machine_draw->queue_redraw(); dragging_selected_attempt = true; dragging_selected = false; drag_from = mb->get_position(); @@ -207,23 +210,54 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv if (closest >= 0) { selected_transition_from = transition_lines[closest].from_node; selected_transition_to = transition_lines[closest].to_node; + selected_transition_index = closest; Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(closest); EditorNode::get_singleton()->push_item(tr.ptr(), "", true); + + if (!transition_lines[closest].multi_transitions.is_empty()) { + selected_transition_index = -1; + selected_multi_transition = transition_lines[closest]; + + Ref<EditorAnimationMultiTransitionEdit> multi; + multi.instantiate(); + multi->add_transition(selected_transition_from, selected_transition_to, tr); + + for (int i = 0; i < transition_lines[closest].multi_transitions.size(); i++) { + int index = transition_lines[closest].multi_transitions[i].transition_index; + + Ref<AnimationNodeStateMachineTransition> transition = state_machine->get_transition(index); + StringName from = transition_lines[closest].multi_transitions[i].from_node; + StringName to = transition_lines[closest].multi_transitions[i].to_node; + + multi->add_transition(from, to, transition); + } + EditorNode::get_singleton()->push_item(multi.ptr(), "", true); + } } - state_machine_draw->update(); + state_machine_draw->queue_redraw(); _update_mode(); } - //end moving node + // End moving node if (mb.is_valid() && dragging_selected_attempt && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { if (dragging_selected) { Ref<AnimationNode> an = state_machine->get_node(selected_node); updating = true; + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Move Node")); - undo_redo->add_do_method(state_machine.ptr(), "set_node_position", selected_node, state_machine->get_node_position(selected_node) + drag_ofs / EDSCALE); - undo_redo->add_undo_method(state_machine.ptr(), "set_node_position", selected_node, state_machine->get_node_position(selected_node)); + + for (int i = 0; i < node_rects.size(); i++) { + if (!selected_nodes.has(node_rects[i].node_name)) { + continue; + } + + undo_redo->add_do_method(state_machine.ptr(), "set_node_position", node_rects[i].node_name, state_machine->get_node_position(node_rects[i].node_name) + drag_ofs / EDSCALE); + undo_redo->add_undo_method(state_machine.ptr(), "set_node_position", node_rects[i].node_name, state_machine->get_node_position(node_rects[i].node_name)); + } + undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); undo_redo->commit_action(); @@ -234,14 +268,15 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv dragging_selected_attempt = false; dragging_selected = false; - state_machine_draw->update(); + state_machine_draw->queue_redraw(); } - //connect nodes + // Connect nodes if (mb.is_valid() && ((tool_select->is_pressed() && mb->is_shift_pressed()) || tool_connect->is_pressed()) && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected connecting = true; + connection_follows_cursor = true; connecting_from = node_rects[i].node_name; connecting_to = mb->get_position(); connecting_to_node = StringName(); @@ -250,51 +285,68 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } } - //end connecting nodes + // End connecting nodes if (mb.is_valid() && connecting && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { if (connecting_to_node != StringName()) { - if (state_machine->has_transition(connecting_from, connecting_to_node)) { - EditorNode::get_singleton()->show_warning(TTR("Transition exists!")); + Ref<AnimationNode> node = state_machine->get_node(connecting_to_node); + Ref<AnimationNodeStateMachine> anodesm = node; + Ref<AnimationNodeEndState> end_node = node; + if (state_machine->has_transition(connecting_from, connecting_to_node) && state_machine->can_edit_node(connecting_to_node) && !anodesm.is_valid()) { + EditorNode::get_singleton()->show_warning(TTR("Transition exists!")); + connecting = false; } else { - Ref<AnimationNodeStateMachineTransition> tr; - tr.instantiate(); - tr->set_switch_mode(AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected())); - - updating = true; - undo_redo->create_action(TTR("Add Transition")); - undo_redo->add_do_method(state_machine.ptr(), "add_transition", connecting_from, connecting_to_node, tr); - undo_redo->add_undo_method(state_machine.ptr(), "remove_transition", connecting_from, connecting_to_node); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); - undo_redo->commit_action(); - updating = false; - - selected_transition_from = connecting_from; - selected_transition_to = connecting_to_node; - - EditorNode::get_singleton()->push_item(tr.ptr(), "", true); - _update_mode(); + if (anodesm.is_valid() || end_node.is_valid()) { + _open_connect_menu(mb->get_position()); + } else { + _add_transition(); + } } + } else { + _open_menu(mb->get_position()); } connecting_to_node = StringName(); - connecting = false; - state_machine_draw->update(); + connection_follows_cursor = false; + state_machine_draw->queue_redraw(); + } + + // Start box selecting + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && tool_select->is_pressed()) { + box_selecting = true; + box_selecting_from = box_selecting_to = state_machine_draw->get_local_mouse_position(); + box_selecting_rect = Rect2(MIN(box_selecting_from.x, box_selecting_to.x), + MIN(box_selecting_from.y, box_selecting_to.y), + ABS(box_selecting_from.x - box_selecting_to.x), + ABS(box_selecting_from.y - box_selecting_to.y)); + + if (mb->is_ctrl_pressed() || mb->is_shift_pressed()) { + previous_selected = selected_nodes; + } else { + selected_nodes.clear(); + previous_selected.clear(); + } + } + + // End box selecting + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed() && box_selecting) { + box_selecting = false; + state_machine_draw->queue_redraw(); + _update_mode(); } Ref<InputEventMouseMotion> mm = p_event; - //pan window - if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE) { + // Pan window + if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) { h_scroll->set_value(h_scroll->get_value() - mm->get_relative().x); v_scroll->set_value(v_scroll->get_value() - mm->get_relative().y); } - //move mouse while connecting - if (mm.is_valid() && connecting) { + // Move mouse while connecting + if (mm.is_valid() && connecting && connection_follows_cursor && !read_only) { connecting_to = mm->get_position(); connecting_to_node = StringName(); - state_machine_draw->update(); + state_machine_draw->queue_redraw(); for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order if (node_rects[i].node_name != connecting_from && node_rects[i].node.has_point(connecting_to)) { //select node since nothing else was selected @@ -304,8 +356,8 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } } - //move mouse while moving a node - if (mm.is_valid() && dragging_selected_attempt) { + // Move mouse while moving a node + if (mm.is_valid() && dragging_selected_attempt && !read_only) { dragging_selected = true; drag_ofs = mm->get_position() - drag_from; snap_x = StringName(); @@ -341,32 +393,59 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } } - state_machine_draw->update(); + state_machine_draw->queue_redraw(); + } + + // Move mouse while moving box select + if (mm.is_valid() && box_selecting) { + box_selecting_to = state_machine_draw->get_local_mouse_position(); + + box_selecting_rect = Rect2(MIN(box_selecting_from.x, box_selecting_to.x), + MIN(box_selecting_from.y, box_selecting_to.y), + ABS(box_selecting_from.x - box_selecting_to.x), + ABS(box_selecting_from.y - box_selecting_to.y)); + + for (int i = 0; i < node_rects.size(); i++) { + bool in_box = node_rects[i].node.intersects(box_selecting_rect); + + if (in_box) { + if (previous_selected.has(node_rects[i].node_name)) { + selected_nodes.erase(node_rects[i].node_name); + } else { + selected_nodes.insert(node_rects[i].node_name); + } + } else { + if (previous_selected.has(node_rects[i].node_name)) { + selected_nodes.insert(node_rects[i].node_name); + } else { + selected_nodes.erase(node_rects[i].node_name); + } + } + } + + state_machine_draw->queue_redraw(); } - //put ibeam (text cursor) over names to make it clearer that they are editable if (mm.is_valid()) { state_machine_draw->grab_focus(); - bool over_text_now = false; - String new_over_node = StringName(); + String new_over_node; int new_over_node_what = -1; if (tool_select->is_pressed()) { - for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order + for (int i = node_rects.size() - 1; i >= 0; i--) { // Inverse to draw order. - if (node_rects[i].name.has_point(mm->get_position())) { - over_text_now = true; - break; + if (!state_machine->can_edit_node(node_rects[i].node_name)) { + continue; // start/end node can't be edited } if (node_rects[i].node.has_point(mm->get_position())) { new_over_node = node_rects[i].node_name; if (node_rects[i].play.has_point(mm->get_position())) { new_over_node_what = 0; - } - if (node_rects[i].edit.has_point(mm->get_position())) { + } else if (node_rects[i].edit.has_point(mm->get_position())) { new_over_node_what = 1; } + break; } } } @@ -374,17 +453,44 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv if (new_over_node != over_node || new_over_node_what != over_node_what) { over_node = new_over_node; over_node_what = new_over_node_what; - state_machine_draw->update(); + state_machine_draw->queue_redraw(); } - if (over_text != over_text_now) { - if (over_text_now) { - state_machine_draw->set_default_cursor_shape(CURSOR_IBEAM); - } else { - state_machine_draw->set_default_cursor_shape(CURSOR_ARROW); + // set tooltip for transition + if (tool_select->is_pressed()) { + int closest = -1; + float closest_d = 1e20; + for (int i = 0; i < transition_lines.size(); i++) { + Vector2 s[2] = { + transition_lines[i].from, + transition_lines[i].to + }; + Vector2 cpoint = Geometry2D::get_closest_point_to_segment(mm->get_position(), s); + float d = cpoint.distance_to(mm->get_position()); + if (d > transition_lines[i].width) { + continue; + } + + if (d < closest_d) { + closest = i; + closest_d = d; + } } - over_text = over_text_now; + if (closest >= 0) { + String from = String(transition_lines[closest].from_node); + String to = String(transition_lines[closest].to_node); + String tooltip = from + " -> " + to; + + for (int i = 0; i < transition_lines[closest].multi_transitions.size(); i++) { + from = String(transition_lines[closest].multi_transitions[i].from_node); + to = String(transition_lines[closest].multi_transitions[i].to_node); + tooltip += "\n" + from + " -> " + to; + } + state_machine_draw->set_tooltip_text(tooltip); + } else { + state_machine_draw->set_tooltip_text(""); + } } } @@ -395,10 +501,497 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } } +Control::CursorShape AnimationNodeStateMachineEditor::get_cursor_shape(const Point2 &p_pos) const { + Control::CursorShape cursor_shape = get_default_cursor_shape(); + if (!read_only) { + // Put ibeam (text cursor) over names to make it clearer that they are editable. + Transform2D xform = panel->get_transform() * state_machine_draw->get_transform(); + Point2 pos = xform.xform_inv(p_pos); + + for (int i = node_rects.size() - 1; i >= 0; i--) { // Inverse to draw order. + if (node_rects[i].node.has_point(pos)) { + if (node_rects[i].name.has_point(pos)) { + if (state_machine->can_edit_node(node_rects[i].node_name)) { + cursor_shape = Control::CURSOR_IBEAM; + } + } + break; + } + } + } + return cursor_shape; +} + +void AnimationNodeStateMachineEditor::_group_selected_nodes() { + if (!selected_nodes.is_empty()) { + if (selected_nodes.size() == 1 && (*selected_nodes.begin() == state_machine->start_node || *selected_nodes.begin() == state_machine->end_node)) + return; + + Ref<AnimationNodeStateMachine> group_sm = memnew(AnimationNodeStateMachine); + Vector2 group_position; + + Vector<NodeUR> nodes_ur; + Vector<TransitionUR> transitions_ur; + + int base = 1; + String base_name = group_sm->get_caption(); + String group_name = base_name; + + while (state_machine->has_node(group_name) && !selected_nodes.has(group_name)) { + base++; + group_name = base_name + " " + itos(base); + } + + updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action("Group"); + + // Move selected nodes to the new state machine + for (const StringName &E : selected_nodes) { + if (!state_machine->can_edit_node(E)) { + continue; + } + + Ref<AnimationNode> node = state_machine->get_node(E); + Vector2 node_position = state_machine->get_node_position(E); + group_position += node_position; + + NodeUR new_node; + new_node.name = E; + new_node.node = node; + new_node.position = node_position; + + nodes_ur.push_back(new_node); + } + + // Add the transitions to the new state machine + for (int i = 0; i < state_machine->get_transition_count(); i++) { + String from = state_machine->get_transition_from(i); + String to = state_machine->get_transition_to(i); + + String local_from = from.get_slicec('/', 0); + String local_to = to.get_slicec('/', 0); + + String old_from = from; + String old_to = to; + + bool from_selected = false; + bool to_selected = false; + + if (selected_nodes.has(local_from) && local_from != state_machine->start_node) { + from_selected = true; + } + if (selected_nodes.has(local_to) && local_to != state_machine->end_node) { + to_selected = true; + } + if (!from_selected && !to_selected) { + continue; + } + + Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(i); + + if (!from_selected) { + from = "../" + old_from; + } + if (!to_selected) { + to = "../" + old_to; + } + + TransitionUR new_tr; + new_tr.new_from = from; + new_tr.new_to = to; + new_tr.old_from = old_from; + new_tr.old_to = old_to; + new_tr.transition = tr; + + transitions_ur.push_back(new_tr); + } + + for (int i = 0; i < nodes_ur.size(); i++) { + undo_redo->add_do_method(state_machine.ptr(), "remove_node", nodes_ur[i].name); + undo_redo->add_undo_method(group_sm.ptr(), "remove_node", nodes_ur[i].name); + } + + undo_redo->add_do_method(state_machine.ptr(), "add_node", group_name, group_sm, group_position / nodes_ur.size()); + undo_redo->add_undo_method(state_machine.ptr(), "remove_node", group_name); + + for (int i = 0; i < nodes_ur.size(); i++) { + undo_redo->add_do_method(group_sm.ptr(), "add_node", nodes_ur[i].name, nodes_ur[i].node, nodes_ur[i].position); + undo_redo->add_undo_method(state_machine.ptr(), "add_node", nodes_ur[i].name, nodes_ur[i].node, nodes_ur[i].position); + } + + for (int i = 0; i < transitions_ur.size(); i++) { + undo_redo->add_do_method(group_sm.ptr(), "add_transition", transitions_ur[i].new_from, transitions_ur[i].new_to, transitions_ur[i].transition); + undo_redo->add_undo_method(state_machine.ptr(), "add_transition", transitions_ur[i].old_from, transitions_ur[i].old_to, transitions_ur[i].transition); + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + + selected_nodes.clear(); + selected_nodes.insert(group_name); + state_machine_draw->queue_redraw(); + accept_event(); + _update_mode(); + } +} + +void AnimationNodeStateMachineEditor::_ungroup_selected_nodes() { + bool find = false; + HashSet<StringName> new_selected_nodes; + + for (const StringName &E : selected_nodes) { + Ref<AnimationNodeStateMachine> group_sm = state_machine->get_node(E); + + if (group_sm.is_valid()) { + find = true; + + Vector2 group_position = state_machine->get_node_position(E); + StringName group_name = E; + + List<AnimationNode::ChildNode> nodes; + group_sm->get_child_nodes(&nodes); + + Vector<NodeUR> nodes_ur; + Vector<TransitionUR> transitions_ur; + + updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action("Ungroup"); + + // Move all child nodes to current state machine + for (int i = 0; i < nodes.size(); i++) { + if (!group_sm->can_edit_node(nodes[i].name)) { + continue; + } + + Vector2 node_position = group_sm->get_node_position(nodes[i].name); + + NodeUR new_node; + new_node.name = nodes[i].name; + new_node.position = node_position; + new_node.node = nodes[i].node; + + nodes_ur.push_back(new_node); + } + + for (int i = 0; i < group_sm->get_transition_count(); i++) { + String from = group_sm->get_transition_from(i); + String to = group_sm->get_transition_to(i); + Ref<AnimationNodeStateMachineTransition> tr = group_sm->get_transition(i); + + TransitionUR new_tr; + new_tr.new_from = from.replace_first("../", ""); + new_tr.new_to = to.replace_first("../", ""); + new_tr.old_from = from; + new_tr.old_to = to; + new_tr.transition = tr; + + transitions_ur.push_back(new_tr); + } + + for (int i = 0; i < nodes_ur.size(); i++) { + undo_redo->add_do_method(group_sm.ptr(), "remove_node", nodes_ur[i].name); + undo_redo->add_undo_method(state_machine.ptr(), "remove_node", nodes_ur[i].name); + } + + undo_redo->add_do_method(state_machine.ptr(), "remove_node", group_name); + undo_redo->add_undo_method(state_machine.ptr(), "add_node", group_name, group_sm, group_position); + + for (int i = 0; i < nodes_ur.size(); i++) { + new_selected_nodes.insert(nodes_ur[i].name); + undo_redo->add_do_method(state_machine.ptr(), "add_node", nodes_ur[i].name, nodes_ur[i].node, nodes_ur[i].position); + undo_redo->add_undo_method(group_sm.ptr(), "add_node", nodes_ur[i].name, nodes_ur[i].node, nodes_ur[i].position); + } + + for (int i = 0; i < transitions_ur.size(); i++) { + if (transitions_ur[i].old_from != state_machine->start_node && transitions_ur[i].old_to != state_machine->end_node) { + undo_redo->add_do_method(state_machine.ptr(), "add_transition", transitions_ur[i].new_from, transitions_ur[i].new_to, transitions_ur[i].transition); + } + + undo_redo->add_undo_method(group_sm.ptr(), "add_transition", transitions_ur[i].old_from, transitions_ur[i].old_to, transitions_ur[i].transition); + } + + for (int i = 0; i < state_machine->get_transition_count(); i++) { + String from = state_machine->get_transition_from(i); + String to = state_machine->get_transition_to(i); + Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(i); + + if (from == group_name || to == group_name) { + undo_redo->add_undo_method(state_machine.ptr(), "add_transition", from, to, tr); + } + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + } + } + + if (find) { + selected_nodes = new_selected_nodes; + selected_node = StringName(); + state_machine_draw->queue_redraw(); + accept_event(); + _update_mode(); + } +} + +void AnimationNodeStateMachineEditor::_open_menu(const Vector2 &p_position) { + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } + + menu->clear(); + animations_menu->clear(); + animations_to_add.clear(); + List<StringName> classes; + classes.sort_custom<StringName::AlphCompare>(); + + ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); + menu->add_submenu_item(TTR("Add Animation"), "animations"); + + if (tree->has_node(tree->get_animation_player())) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(tree->get_node(tree->get_animation_player())); + if (ap) { + List<StringName> names; + ap->get_animation_list(&names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + animations_menu->add_icon_item(get_theme_icon("Animation", "EditorIcons"), E->get()); + animations_to_add.push_back(E->get()); + } + } + } + + for (List<StringName>::Element *E = classes.front(); E; E = E->next()) { + String name = String(E->get()).replace_first("AnimationNode", ""); + if (name == "Animation" || name == "StartState" || name == "EndState") { + continue; // nope + } + int idx = menu->get_item_count(); + menu->add_item(vformat(TTR("Add %s"), name), idx); + menu->set_item_metadata(idx, E->get()); + } + Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard(); + + if (clipb.is_valid()) { + menu->add_separator(); + menu->add_item(TTR("Paste"), MENU_PASTE); + } + menu->add_separator(); + menu->add_item(TTR("Load..."), MENU_LOAD_FILE); + + menu->set_position(state_machine_draw->get_screen_transform().xform(p_position)); + menu->popup(); + add_node_pos = p_position / EDSCALE + state_machine->get_graph_offset(); +} + +void AnimationNodeStateMachineEditor::_open_connect_menu(const Vector2 &p_position) { + ERR_FAIL_COND(connecting_to_node == StringName()); + + Ref<AnimationNode> node = state_machine->get_node(connecting_to_node); + Ref<AnimationNodeStateMachine> anodesm = node; + Ref<AnimationNodeEndState> end_node = node; + ERR_FAIL_COND(!anodesm.is_valid() && !end_node.is_valid()); + + connect_menu->clear(); + state_machine_menu->clear(); + end_menu->clear(); + nodes_to_connect.clear(); + + for (int i = connect_menu->get_child_count() - 1; i >= 0; i--) { + Node *child = connect_menu->get_child(i); + + if (child->is_class("PopupMenu")) { + connect_menu->remove_child(child); + } + } + + connect_menu->reset_size(); + state_machine_menu->reset_size(); + end_menu->reset_size(); + + if (anodesm.is_valid()) { + _create_submenu(connect_menu, anodesm, connecting_to_node, connecting_to_node); + } else { + _create_submenu(connect_menu, state_machine, connecting_to_node, connecting_to_node, true); + } + + connect_menu->add_submenu_item(TTR("To") + " Animation", connecting_to_node); + + if (state_machine_menu->get_item_count() > 0 || !end_node.is_valid()) { + connect_menu->add_submenu_item(TTR("To") + " StateMachine", "state_machines"); + connect_menu->add_child(state_machine_menu); + } + + if (end_node.is_valid()) { + connect_menu->add_submenu_item(TTR("To") + " End", "end_nodes"); + connect_menu->add_child(end_menu); + } else { + state_machine_menu->add_item(connecting_to_node, nodes_to_connect.size()); + } + + nodes_to_connect.push_back(connecting_to_node); + + if (nodes_to_connect.size() == 1) { + _add_transition(); + return; + } + + connect_menu->set_position(state_machine_draw->get_screen_transform().xform(p_position)); + connect_menu->popup(); +} + +bool AnimationNodeStateMachineEditor::_create_submenu(PopupMenu *p_menu, Ref<AnimationNodeStateMachine> p_nodesm, const StringName &p_name, const StringName &p_path, bool from_root, Vector<Ref<AnimationNodeStateMachine>> p_parents) { + String prev_path; + Vector<Ref<AnimationNodeStateMachine>> parents = p_parents; + + if (from_root && p_nodesm->get_prev_state_machine() == nullptr) { + return false; + } + + if (from_root) { + AnimationNodeStateMachine *prev = p_nodesm->get_prev_state_machine(); + + while (prev != nullptr) { + parents.push_back(prev); + p_nodesm = Ref<AnimationNodeStateMachine>(prev); + prev_path += "../"; + prev = prev->get_prev_state_machine(); + } + end_menu->add_item("Root", nodes_to_connect.size()); + nodes_to_connect.push_back(prev_path + state_machine->end_node); + prev_path.remove_at(prev_path.size() - 1); + } + + List<StringName> nodes; + p_nodesm->get_node_list(&nodes); + + PopupMenu *nodes_menu = memnew(PopupMenu); + nodes_menu->set_name(p_name); + nodes_menu->connect("id_pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_connect_to)); + p_menu->add_child(nodes_menu); + + bool node_added = false; + for (const StringName &E : nodes) { + if (p_nodesm->can_edit_node(E)) { + Ref<AnimationNodeStateMachine> ansm = p_nodesm->get_node(E); + + String path; + if (from_root) { + path = prev_path + "/" + E; + } else { + path = String(p_path) + "/" + E; + } + + if (ansm == state_machine) { + end_menu->add_item(E, nodes_to_connect.size()); + nodes_to_connect.push_back(state_machine->end_node); + continue; + } + + if (ansm.is_valid()) { + bool parent_found = false; + + for (int i = 0; i < parents.size(); i++) { + if (parents[i] == ansm) { + path = path.replace_first("/../" + E, ""); + parent_found = true; + break; + } + } + + if (parent_found) { + end_menu->add_item(E, nodes_to_connect.size()); + nodes_to_connect.push_back(path + "/" + state_machine->end_node); + } else { + state_machine_menu->add_item(E, nodes_to_connect.size()); + nodes_to_connect.push_back(path); + } + + if (_create_submenu(nodes_menu, ansm, E, path, false, parents)) { + nodes_menu->add_submenu_item(E, E); + node_added = true; + } + } else { + nodes_menu->add_item(E, nodes_to_connect.size()); + nodes_to_connect.push_back(path); + node_added = true; + } + } + } + + return node_added; +} + +void AnimationNodeStateMachineEditor::_stop_connecting() { + connecting = false; + state_machine_draw->queue_redraw(); +} + +void AnimationNodeStateMachineEditor::_delete_selected() { + TreeItem *item = delete_tree->get_next_selected(nullptr); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + while (item) { + if (!updating) { + updating = true; + selected_multi_transition = TransitionLine(); + undo_redo->create_action("Transition(s) Removed"); + } + + Vector<String> path = item->get_text(0).split(" -> "); + + selected_transition_from = path[0]; + selected_transition_to = path[1]; + _erase_selected(true); + + item = delete_tree->get_next_selected(item); + } + + if (updating) { + undo_redo->commit_action(); + updating = false; + } +} + +void AnimationNodeStateMachineEditor::_delete_all() { + Vector<TransitionLine> multi_transitions = selected_multi_transition.multi_transitions; + selected_multi_transition = TransitionLine(); + + updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action("Transition(s) Removed"); + _erase_selected(true); + for (int i = 0; i < multi_transitions.size(); i++) { + selected_transition_from = multi_transitions[i].from_node; + selected_transition_to = multi_transitions[i].to_node; + _erase_selected(true); + } + undo_redo->commit_action(); + updating = false; + + delete_window->hide(); +} + +void AnimationNodeStateMachineEditor::_delete_tree_draw() { + TreeItem *item = delete_tree->get_next_selected(nullptr); + while (item) { + delete_window->get_cancel_button()->set_disabled(false); + return; + } + delete_window->get_cancel_button()->set_disabled(true); +} + void AnimationNodeStateMachineEditor::_file_opened(const String &p_file) { file_loaded = ResourceLoader::load(p_file); if (file_loaded.is_valid()) { _add_menu_type(MENU_LOAD_FILE_CONFIRM); + } else { + EditorNode::get_singleton()->show_warning(TTR("This type of node can't be used. Only animation nodes are allowed.")); } } @@ -450,15 +1043,16 @@ void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) { } updating = true; - undo_redo->create_action(TTR("Add Node")); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Add Node and Transition")); undo_redo->add_do_method(state_machine.ptr(), "add_node", name, node, add_node_pos); undo_redo->add_undo_method(state_machine.ptr(), "remove_node", name); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); + connecting_to_node = name; + _add_transition(true); undo_redo->commit_action(); updating = false; - state_machine_draw->update(); + state_machine_draw->queue_redraw(); } void AnimationNodeStateMachineEditor::_add_animation_type(int p_index) { @@ -467,7 +1061,7 @@ void AnimationNodeStateMachineEditor::_add_animation_type(int p_index) { anim->set_animation(animations_to_add[p_index]); - String base_name = animations_to_add[p_index]; + String base_name = animations_to_add[p_index].validate_node_name(); int base = 1; String name = base_name; while (state_machine->has_node(name)) { @@ -476,18 +1070,64 @@ void AnimationNodeStateMachineEditor::_add_animation_type(int p_index) { } updating = true; - undo_redo->create_action(TTR("Add Node")); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Add Node and Transition")); undo_redo->add_do_method(state_machine.ptr(), "add_node", name, anim, add_node_pos); undo_redo->add_undo_method(state_machine.ptr(), "remove_node", name); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); + connecting_to_node = name; + _add_transition(true); undo_redo->commit_action(); updating = false; - state_machine_draw->update(); + state_machine_draw->queue_redraw(); +} + +void AnimationNodeStateMachineEditor::_connect_to(int p_index) { + connecting_to_node = nodes_to_connect[p_index]; + _add_transition(); +} + +void AnimationNodeStateMachineEditor::_add_transition(const bool p_nested_action) { + if (connecting_from != StringName() && connecting_to_node != StringName()) { + if (state_machine->has_transition(connecting_from, connecting_to_node)) { + EditorNode::get_singleton()->show_warning("Transition exists!"); + connecting = false; + return; + } + + Ref<AnimationNodeStateMachineTransition> tr; + tr.instantiate(); + tr->set_advance_mode(auto_advance->is_pressed() ? AnimationNodeStateMachineTransition::AdvanceMode::ADVANCE_MODE_AUTO : AnimationNodeStateMachineTransition::AdvanceMode::ADVANCE_MODE_ENABLED); + tr->set_switch_mode(AnimationNodeStateMachineTransition::SwitchMode(switch_mode->get_selected())); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + if (!p_nested_action) { + updating = true; + undo_redo->create_action(TTR("Add Transition")); + } + + undo_redo->add_do_method(state_machine.ptr(), "add_transition", connecting_from, connecting_to_node, tr); + undo_redo->add_undo_method(state_machine.ptr(), "remove_transition", connecting_from, connecting_to_node); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + + if (!p_nested_action) { + undo_redo->commit_action(); + updating = false; + } + + selected_transition_from = connecting_from; + selected_transition_to = connecting_to_node; + selected_transition_index = transition_lines.size(); + + EditorNode::get_singleton()->push_item(tr.ptr(), "", true); + _update_mode(); + } + + connecting = false; } -void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance) { +void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_multi_transitions) { Color linecolor = get_theme_color(SNAME("font_color"), SNAME("Label")); Color icon_color(1, 1, 1); Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); @@ -498,7 +1138,7 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co accent.a *= 0.6; } - Ref<Texture2D> icons[6] = { + const Ref<Texture2D> icons[] = { get_theme_icon(SNAME("TransitionImmediateBig"), SNAME("EditorIcons")), get_theme_icon(SNAME("TransitionSyncBig"), SNAME("EditorIcons")), get_theme_icon(SNAME("TransitionEndBig"), SNAME("EditorIcons")), @@ -506,6 +1146,7 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co get_theme_icon(SNAME("TransitionSyncAutoBig"), SNAME("EditorIcons")), get_theme_icon(SNAME("TransitionEndAutoBig"), SNAME("EditorIcons")) }; + const int ICON_COUNT = sizeof(icons) / sizeof(*icons); if (p_selected) { state_machine_draw->draw_line(p_from, p_to, accent, 6); @@ -513,63 +1154,88 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co if (p_travel) { linecolor = accent; - linecolor.set_hsv(1.0, linecolor.get_s(), linecolor.get_v()); } + state_machine_draw->draw_line(p_from, p_to, linecolor, 2); - Ref<Texture2D> icon = icons[p_mode + (p_auto_advance ? 3 : 0)]; + if (p_fade_ratio > 0.0) { + Color fade_linecolor = accent; + fade_linecolor.set_hsv(1.0, fade_linecolor.get_s(), fade_linecolor.get_v()); + state_machine_draw->draw_line(p_from, p_from.lerp(p_to, p_fade_ratio), fade_linecolor, 2); + } + int icon_index = p_mode + (p_auto_advance ? ICON_COUNT / 2 : 0); + ERR_FAIL_COND(icon_index >= ICON_COUNT); + Ref<Texture2D> icon = icons[icon_index]; Transform2D xf; - xf.elements[0] = (p_to - p_from).normalized(); - xf.elements[1] = xf.elements[0].orthogonal(); - xf.elements[2] = (p_from + p_to) * 0.5 - xf.elements[1] * icon->get_height() * 0.5 - xf.elements[0] * icon->get_height() * 0.5; + xf.columns[0] = (p_to - p_from).normalized(); + xf.columns[1] = xf.columns[0].orthogonal(); + xf.columns[2] = (p_from + p_to) * 0.5 - xf.columns[1] * icon->get_height() * 0.5 - xf.columns[0] * icon->get_height() * 0.5; state_machine_draw->draw_set_transform_matrix(xf); - state_machine_draw->draw_texture(icon, Vector2(), icon_color); + if (p_multi_transitions) { + state_machine_draw->draw_texture(icons[0], Vector2(-icon->get_width(), 0), icon_color); + state_machine_draw->draw_texture(icons[0], Vector2(), icon_color); + state_machine_draw->draw_texture(icons[0], Vector2(icon->get_width(), 0), icon_color); + } else { + state_machine_draw->draw_texture(icon, Vector2(), icon_color); + } state_machine_draw->draw_set_transform_matrix(Transform2D()); } -void AnimationNodeStateMachineEditor::_clip_src_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect) { - if (r_to == r_from) { +void AnimationNodeStateMachineEditor::_clip_src_line_to_rect(Vector2 &r_from, const Vector2 &p_to, const Rect2 &p_rect) { + if (p_to == r_from) { return; } //this could be optimized... - Vector2 n = (r_to - r_from).normalized(); + Vector2 n = (p_to - r_from).normalized(); while (p_rect.has_point(r_from)) { r_from += n; } } -void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect) { - if (r_to == r_from) { +void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(const Vector2 &p_from, Vector2 &r_to, const Rect2 &p_rect) { + if (r_to == p_from) { return; } //this could be optimized... - Vector2 n = (r_to - r_from).normalized(); + Vector2 n = (r_to - p_from).normalized(); while (p_rect.has_point(r_to)) { r_to -= n; } } void AnimationNodeStateMachineEditor::_state_machine_draw() { - Ref<AnimationNodeStateMachinePlayback> playback = AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } + + Ref<AnimationNodeStateMachinePlayback> playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); - Ref<StyleBox> style = get_theme_stylebox(SNAME("state_machine_frame"), SNAME("GraphNode")); - Ref<StyleBox> style_selected = get_theme_stylebox(SNAME("state_machine_selectedframe"), SNAME("GraphNode")); + Ref<StyleBoxFlat> style = get_theme_stylebox(SNAME("state_machine_frame"), SNAME("GraphNode")); + Ref<StyleBoxFlat> style_selected = get_theme_stylebox(SNAME("state_machine_selected_frame"), SNAME("GraphNode")); Ref<Font> font = get_theme_font(SNAME("title_font"), SNAME("GraphNode")); int font_size = get_theme_font_size(SNAME("title_font_size"), SNAME("GraphNode")); Color font_color = get_theme_color(SNAME("title_color"), SNAME("GraphNode")); Ref<Texture2D> play = get_theme_icon(SNAME("Play"), SNAME("EditorIcons")); - Ref<Texture2D> auto_play = get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons")); Ref<Texture2D> edit = get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")); Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); Color linecolor = get_theme_color(SNAME("font_color"), SNAME("Label")); linecolor.a *= 0.3; Ref<StyleBox> playing_overlay = get_theme_stylebox(SNAME("position"), SNAME("GraphNode")); + Ref<StyleBoxFlat> start_overlay = style->duplicate(); + start_overlay->set_border_width_all(1 * EDSCALE); + start_overlay->set_border_color(Color::html("#80f6cf")); + + Ref<StyleBoxFlat> end_overlay = style->duplicate(); + end_overlay->set_border_width_all(1 * EDSCALE); + end_overlay->set_border_color(Color::html("#f26661")); + bool playing = false; StringName current; StringName blend_from; @@ -578,7 +1244,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { if (playback.is_valid()) { playing = playback->is_playing(); current = playback->get_current_node(); - blend_from = playback->get_blend_from_node(); + blend_from = playback->get_fading_from_node(); travel_path = playback->get_travel_path(); } @@ -610,23 +1276,26 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { for (const StringName &E : nodes) { Ref<AnimationNode> anode = state_machine->get_node(E); String name = E; - bool needs_editor = EditorNode::get_singleton()->item_has_editor(anode.ptr()); - Ref<StyleBox> sb = E == selected_node ? style_selected : style; + bool needs_editor = AnimationTreeEditor::get_singleton()->can_edit(anode); + Ref<StyleBox> sb = selected_nodes.has(E) ? style_selected : style; Size2 s = sb->get_minimum_size(); - int strsize = font->get_string_size(name, font_size).width; + int strsize = font->get_string_size(name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; s.width += strsize; s.height += MAX(font->get_height(font_size), play->get_height()); s.width += sep + play->get_width(); + if (needs_editor) { s.width += sep + edit->get_width(); } Vector2 offset; offset += state_machine->get_node_position(E) * EDSCALE; - if (selected_node == E && dragging_selected) { + + if (selected_nodes.has(E) && dragging_selected) { offset += drag_ofs; } + offset -= s / 2; offset = offset.floor(); @@ -665,7 +1334,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { } } - _connection_draw(from, to, AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected()), true, false, false, false); + _connection_draw(from, to, AnimationNodeStateMachineTransition::SwitchMode(switch_mode->get_selected()), true, false, false, 0.0, false, false); } Ref<Texture2D> tr_reference_icon = get_theme_icon(SNAME("TransitionImmediateBig"), SNAME("EditorIcons")); @@ -674,77 +1343,101 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { //draw transition lines for (int i = 0; i < state_machine->get_transition_count(); i++) { TransitionLine tl; + tl.transition_index = i; tl.from_node = state_machine->get_transition_from(i); - Vector2 ofs_from = (dragging_selected && tl.from_node == selected_node) ? drag_ofs : Vector2(); - tl.from = (state_machine->get_node_position(tl.from_node) * EDSCALE) + ofs_from - state_machine->get_graph_offset() * EDSCALE; + StringName local_from = String(tl.from_node).get_slicec('/', 0); + local_from = local_from == ".." ? state_machine->start_node : local_from; + Vector2 ofs_from = (dragging_selected && selected_nodes.has(local_from)) ? drag_ofs : Vector2(); + tl.from = (state_machine->get_node_position(local_from) * EDSCALE) + ofs_from - state_machine->get_graph_offset() * EDSCALE; tl.to_node = state_machine->get_transition_to(i); - Vector2 ofs_to = (dragging_selected && tl.to_node == selected_node) ? drag_ofs : Vector2(); - tl.to = (state_machine->get_node_position(tl.to_node) * EDSCALE) + ofs_to - state_machine->get_graph_offset() * EDSCALE; + StringName local_to = String(tl.to_node).get_slicec('/', 0); + local_to = local_to == ".." ? state_machine->end_node : local_to; + Vector2 ofs_to = (dragging_selected && selected_nodes.has(local_to)) ? drag_ofs : Vector2(); + tl.to = (state_machine->get_node_position(local_to) * EDSCALE) + ofs_to - state_machine->get_graph_offset() * EDSCALE; Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(i); - tl.disabled = tr->is_disabled(); - tl.auto_advance = tr->has_auto_advance(); + tl.disabled = bool(tr->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_DISABLED); + tl.auto_advance = bool(tr->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_AUTO); tl.advance_condition_name = tr->get_advance_condition_name(); tl.advance_condition_state = false; tl.mode = tr->get_switch_mode(); tl.width = tr_bidi_offset; + tl.travel = false; + tl.fade_ratio = 0.0; + tl.hidden = false; - if (state_machine->has_transition(tl.to_node, tl.from_node)) { //offset if same exists + if (state_machine->has_local_transition(local_to, local_from)) { //offset if same exists Vector2 offset = -(tl.from - tl.to).normalized().orthogonal() * tr_bidi_offset; tl.from += offset; tl.to += offset; } for (int j = 0; j < node_rects.size(); j++) { - if (node_rects[j].node_name == tl.from_node) { + if (node_rects[j].node_name == local_from) { _clip_src_line_to_rect(tl.from, tl.to, node_rects[j].node); } - if (node_rects[j].node_name == tl.to_node) { + if (node_rects[j].node_name == local_to) { _clip_dst_line_to_rect(tl.from, tl.to, node_rects[j].node); } } - bool selected = selected_transition_from == tl.from_node && selected_transition_to == tl.to_node; + tl.selected = selected_transition_from == tl.from_node && selected_transition_to == tl.to_node; - bool travel = false; - - if (blend_from == tl.from_node && current == tl.to_node) { - travel = true; + if (blend_from == local_from && current == local_to) { + tl.travel = true; + tl.fade_ratio = MIN(1.0, fading_pos / fading_time); } if (travel_path.size()) { - if (current == tl.from_node && travel_path[0] == tl.to_node) { - travel = true; + if (current == local_from && travel_path[0] == local_to) { + tl.travel = true; } else { for (int j = 0; j < travel_path.size() - 1; j++) { - if (travel_path[j] == tl.from_node && travel_path[j + 1] == tl.to_node) { - travel = true; + if (travel_path[j] == local_from && travel_path[j + 1] == local_to) { + tl.travel = true; break; } } } } - bool auto_advance = tl.auto_advance; StringName fullpath = AnimationTreeEditor::get_singleton()->get_base_path() + String(tl.advance_condition_name); - if (tl.advance_condition_name != StringName() && bool(AnimationTreeEditor::get_singleton()->get_tree()->get(fullpath))) { + if (tl.advance_condition_name != StringName() && bool(tree->get(fullpath))) { tl.advance_condition_state = true; - auto_advance = true; + tl.auto_advance = true; } - _connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, selected, travel, auto_advance); + // check if already have this local transition + for (int j = 0; j < transition_lines.size(); j++) { + StringName from = String(transition_lines[j].from_node).get_slicec('/', 0); + StringName to = String(transition_lines[j].to_node).get_slicec('/', 0); + from = from == ".." ? state_machine->start_node : from; + to = to == ".." ? state_machine->end_node : to; + + if (from == local_from && to == local_to) { + tl.hidden = true; + transition_lines.write[j].disabled = transition_lines[j].disabled && tl.disabled; + transition_lines.write[j].multi_transitions.push_back(tl); + } + } transition_lines.push_back(tl); } + for (int i = 0; i < transition_lines.size(); i++) { + TransitionLine tl = transition_lines[i]; + if (!tl.hidden) { + _connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, tl.selected, tl.travel, tl.fade_ratio, tl.auto_advance, !tl.multi_transitions.is_empty()); + } + } + //draw actual nodes for (int i = 0; i < node_rects.size(); i++) { String name = node_rects[i].node_name; Ref<AnimationNode> anode = state_machine->get_node(name); bool needs_editor = AnimationTreeEditor::get_singleton()->can_edit(anode); - Ref<StyleBox> sb = name == selected_node ? style_selected : style; - int strsize = font->get_string_size(name, font_size).width; - + Ref<StyleBox> sb = selected_nodes.has(name) ? style_selected : style; + int strsize = font->get_string_size(name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; NodeRect &nr = node_rects.write[i]; Vector2 offset = nr.node.position; @@ -755,18 +1448,16 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { //now scroll it to draw state_machine_draw->draw_style_box(sb, nr.node); - if (playing && (blend_from == name || current == name || travel_path.find(name) != -1)) { - state_machine_draw->draw_style_box(playing_overlay, nr.node); + if (state_machine->start_node == name) { + state_machine_draw->draw_style_box(sb == style_selected ? style_selected : start_overlay, nr.node); } - bool onstart = state_machine->get_start_node() == name; - if (onstart) { - state_machine_draw->draw_string(font, offset + Vector2(0, -font->get_height(font_size) - 3 * EDSCALE + font->get_ascent(font_size)), TTR("Start"), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); + if (state_machine->end_node == name) { + state_machine_draw->draw_style_box(sb == style_selected ? style_selected : end_overlay, nr.node); } - if (state_machine->get_end_node() == name) { - int endofs = nr.node.size.x - font->get_string_size(TTR("End"), font_size).x; - state_machine_draw->draw_string(font, offset + Vector2(endofs, -font->get_height(font_size) - 3 * EDSCALE + font->get_ascent(font_size)), TTR("End"), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); + if (playing && (blend_from == name || current == name || travel_path.has(name))) { + state_machine_draw->draw_style_box(playing_overlay, nr.node); } offset.x += sb->get_offset().x; @@ -774,13 +1465,14 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { nr.play.position = offset + Vector2(0, (h - play->get_height()) / 2).floor(); nr.play.size = play->get_size(); - Ref<Texture2D> play_tex = onstart ? auto_play : play; + Ref<Texture2D> play_tex = play; if (over_node == name && over_node_what == 0) { state_machine_draw->draw_texture(play_tex, nr.play.position, accent); } else { state_machine_draw->draw_texture(play_tex, nr.play.position); } + offset.x += sep + play->get_width(); nr.name.position = offset + Vector2(0, (h - font->get_height(font_size)) / 2).floor(); @@ -798,10 +1490,14 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { } else { state_machine_draw->draw_texture(edit, nr.edit.position); } - offset.x += sep + edit->get_width(); } } + //draw box select + if (box_selecting) { + state_machine_draw->draw_rect(box_selecting_rect, Color(0.7, 0.7, 1.0, 0.3)); + } + scroll_range.position -= state_machine_draw->get_size(); scroll_range.size += state_machine_draw->get_size() * 2.0; @@ -818,19 +1514,27 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { v_scroll->set_value(state_machine->get_graph_offset().y); updating = false; - state_machine_play_pos->update(); + state_machine_play_pos->queue_redraw(); } -void AnimationNodeStateMachineEditor::_state_machine_pos_draw() { - Ref<AnimationNodeStateMachinePlayback> playback = AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); +void AnimationNodeStateMachineEditor::_state_machine_pos_draw_individual(String p_name, float p_ratio) { + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } + Ref<AnimationNodeStateMachinePlayback> playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); if (!playback.is_valid() || !playback->is_playing()) { return; } + if (p_name == state_machine->start_node || p_name == state_machine->end_node || p_name.is_empty()) { + return; + } + int idx = -1; for (int i = 0; i < node_rects.size(); i++) { - if (node_rects[i].node_name == playback->get_current_node()) { + if (node_rects[i].node_name == p_name) { idx = i; break; } @@ -854,10 +1558,7 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw() { } to.y = from.y; - float len = MAX(0.0001, current_length); - - float pos = CLAMP(play_pos, 0, len); - float c = pos / len; + float c = p_ratio; Color fg = get_theme_color(SNAME("font_color"), SNAME("Label")); Color bg = fg; bg.a *= 0.3; @@ -869,6 +1570,32 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw() { state_machine_play_pos->draw_line(from, to, fg, 2); } +void AnimationNodeStateMachineEditor::_state_machine_pos_draw_all() { + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } + + Ref<AnimationNodeStateMachinePlayback> playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); + if (!playback.is_valid() || !playback->is_playing()) { + return; + } + + { + float len = MAX(0.0001, current_length); + float pos = CLAMP(current_play_pos, 0, len); + float c = pos / len; + _state_machine_pos_draw_individual(playback->get_current_node(), c); + } + + { + float len = MAX(0.0001, fade_from_length); + float pos = CLAMP(fade_from_current_play_pos, 0, len); + float c = pos / len; + _state_machine_pos_draw_individual(playback->get_fading_from_node(), c); + } +} + void AnimationNodeStateMachineEditor::_update_graph() { if (updating) { return; @@ -876,175 +1603,206 @@ void AnimationNodeStateMachineEditor::_update_graph() { updating = true; - state_machine_draw->update(); + state_machine_draw->queue_redraw(); updating = false; } void AnimationNodeStateMachineEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) { - error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - - tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); - tool_create->set_icon(get_theme_icon(SNAME("ToolAddNode"), SNAME("EditorIcons"))); - tool_connect->set_icon(get_theme_icon(SNAME("ToolConnect"), SNAME("EditorIcons"))); - - transition_mode->clear(); - transition_mode->add_icon_item(get_theme_icon(SNAME("TransitionImmediate"), SNAME("EditorIcons")), TTR("Immediate")); - transition_mode->add_icon_item(get_theme_icon(SNAME("TransitionSync"), SNAME("EditorIcons")), TTR("Sync")); - transition_mode->add_icon_item(get_theme_icon(SNAME("TransitionEnd"), SNAME("EditorIcons")), TTR("At End")); - - tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - tool_autoplay->set_icon(get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons"))); - tool_end->set_icon(get_theme_icon(SNAME("AutoEnd"), SNAME("EditorIcons"))); - - play_mode->clear(); - play_mode->add_icon_item(get_theme_icon(SNAME("PlayTravel"), SNAME("EditorIcons")), TTR("Travel")); - play_mode->add_icon_item(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")), TTR("Immediate")); - } - - if (p_what == NOTIFICATION_PROCESS) { - String error; - - Ref<AnimationNodeStateMachinePlayback> playback = AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); - - if (error_time > 0) { - error = error_text; - error_time -= get_process_delta_time(); - } else if (!AnimationTreeEditor::get_singleton()->get_tree()->is_active()) { - error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); - } else if (AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { - error = AnimationTreeEditor::get_singleton()->get_tree()->get_invalid_state_reason(); - /*} else if (state_machine->get_parent().is_valid() && state_machine->get_parent()->is_class("AnimationNodeStateMachine")) { - if (state_machine->get_start_node() == StringName() || state_machine->get_end_node() == StringName()) { - error = TTR("Start and end nodes are needed for a sub-transition."); - }*/ - } else if (playback.is_null()) { - error = vformat(TTR("No playback resource set at path: %s."), AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); - } + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: { + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + + tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_create->set_icon(get_theme_icon(SNAME("ToolAddNode"), SNAME("EditorIcons"))); + tool_connect->set_icon(get_theme_icon(SNAME("ToolConnect"), SNAME("EditorIcons"))); + + switch_mode->clear(); + switch_mode->add_icon_item(get_theme_icon(SNAME("TransitionImmediate"), SNAME("EditorIcons")), TTR("Immediate")); + switch_mode->add_icon_item(get_theme_icon(SNAME("TransitionSync"), SNAME("EditorIcons")), TTR("Sync")); + switch_mode->add_icon_item(get_theme_icon(SNAME("TransitionEnd"), SNAME("EditorIcons")), TTR("At End")); + + auto_advance->set_icon(get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons"))); + + tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + tool_group->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); + tool_ungroup->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); + + play_mode->clear(); + play_mode->add_icon_item(get_theme_icon(SNAME("PlayTravel"), SNAME("EditorIcons")), TTR("Travel")); + play_mode->add_icon_item(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")), TTR("Immediate")); + } break; + + case NOTIFICATION_PROCESS: { + AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree(); + if (!tree) { + return; + } - if (error != error_label->get_text()) { - error_label->set_text(error); - if (!error.is_empty()) { - error_panel->show(); - } else { - error_panel->hide(); + String error; + + Ref<AnimationNodeStateMachinePlayback> playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); + + if (error_time > 0) { + error = error_text; + error_time -= get_process_delta_time(); + } else if (!tree->is_active()) { + error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); + } else if (tree->is_state_invalid()) { + error = tree->get_invalid_state_reason(); + /*} else if (state_machine->get_parent().is_valid() && state_machine->get_parent()->is_class("AnimationNodeStateMachine")) { + if (state_machine->get_start_node() == StringName() || state_machine->get_end_node() == StringName()) { + error = TTR("Start and end nodes are needed for a sub-transition."); + }*/ + } else if (playback.is_null()) { + error = vformat(TTR("No playback resource set at path: %s."), AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); } - } - for (int i = 0; i < transition_lines.size(); i++) { - int tidx = -1; - for (int j = 0; j < state_machine->get_transition_count(); j++) { - if (transition_lines[i].from_node == state_machine->get_transition_from(j) && transition_lines[i].to_node == state_machine->get_transition_to(j)) { - tidx = j; - break; + if (error != error_label->get_text()) { + error_label->set_text(error); + if (!error.is_empty()) { + error_panel->show(); + } else { + error_panel->hide(); } } - if (tidx == -1) { //missing transition, should redraw - state_machine_draw->update(); - break; - } + for (int i = 0; i < transition_lines.size(); i++) { + int tidx = -1; + for (int j = 0; j < state_machine->get_transition_count(); j++) { + if (transition_lines[i].from_node == state_machine->get_transition_from(j) && transition_lines[i].to_node == state_machine->get_transition_to(j)) { + tidx = j; + break; + } + } - if (transition_lines[i].disabled != state_machine->get_transition(tidx)->is_disabled()) { - state_machine_draw->update(); - break; - } + if (tidx == -1) { //missing transition, should redraw + state_machine_draw->queue_redraw(); + break; + } - if (transition_lines[i].auto_advance != state_machine->get_transition(tidx)->has_auto_advance()) { - state_machine_draw->update(); - break; - } + if (transition_lines[i].disabled != bool(state_machine->get_transition(tidx)->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_DISABLED)) { + state_machine_draw->queue_redraw(); + break; + } - if (transition_lines[i].advance_condition_name != state_machine->get_transition(tidx)->get_advance_condition_name()) { - state_machine_draw->update(); - break; - } + if (transition_lines[i].auto_advance != bool(state_machine->get_transition(tidx)->get_advance_mode() == AnimationNodeStateMachineTransition::ADVANCE_MODE_AUTO)) { + state_machine_draw->queue_redraw(); + break; + } - if (transition_lines[i].mode != state_machine->get_transition(tidx)->get_switch_mode()) { - state_machine_draw->update(); - break; - } + if (transition_lines[i].advance_condition_name != state_machine->get_transition(tidx)->get_advance_condition_name()) { + state_machine_draw->queue_redraw(); + break; + } - bool acstate = transition_lines[i].advance_condition_name != StringName() && bool(AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + String(transition_lines[i].advance_condition_name))); + if (transition_lines[i].mode != state_machine->get_transition(tidx)->get_switch_mode()) { + state_machine_draw->queue_redraw(); + break; + } - if (transition_lines[i].advance_condition_state != acstate) { - state_machine_draw->update(); - break; + bool acstate = transition_lines[i].advance_condition_name != StringName() && bool(tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + String(transition_lines[i].advance_condition_name))); + + if (transition_lines[i].advance_condition_state != acstate) { + state_machine_draw->queue_redraw(); + break; + } } - } - bool same_travel_path = true; - Vector<StringName> tp; - bool is_playing = false; - StringName current_node; - StringName blend_from_node; - play_pos = 0; - current_length = 0; - - if (playback.is_valid()) { - tp = playback->get_travel_path(); - is_playing = playback->is_playing(); - current_node = playback->get_current_node(); - blend_from_node = playback->get_blend_from_node(); - play_pos = playback->get_current_play_pos(); - current_length = playback->get_current_length(); - } + bool same_travel_path = true; + Vector<StringName> tp; + bool is_playing = false; + StringName current_node; + StringName fading_from_node; + + current_play_pos = 0; + current_length = 0; + + fade_from_current_play_pos = 0; + fade_from_length = 0; + + fading_time = 0; + fading_pos = 0; + + if (playback.is_valid()) { + tp = playback->get_travel_path(); + is_playing = playback->is_playing(); + current_node = playback->get_current_node(); + fading_from_node = playback->get_fading_from_node(); + current_play_pos = playback->get_current_play_pos(); + current_length = playback->get_current_length(); + fade_from_current_play_pos = playback->get_fade_from_play_pos(); + fade_from_length = playback->get_fade_from_length(); + fading_time = playback->get_fading_time(); + fading_pos = playback->get_fading_pos(); + } - { - if (last_travel_path.size() != tp.size()) { - same_travel_path = false; - } else { - for (int i = 0; i < last_travel_path.size(); i++) { - if (last_travel_path[i] != tp[i]) { - same_travel_path = false; - break; + { + if (last_travel_path.size() != tp.size()) { + same_travel_path = false; + } else { + for (int i = 0; i < last_travel_path.size(); i++) { + if (last_travel_path[i] != tp[i]) { + same_travel_path = false; + break; + } } } } - } - //update if travel state changed - if (!same_travel_path || last_active != is_playing || last_current_node != current_node || last_blend_from_node != blend_from_node) { - state_machine_draw->update(); - last_travel_path = tp; - last_current_node = current_node; - last_active = is_playing; - last_blend_from_node = blend_from_node; - state_machine_play_pos->update(); - } + //redraw if travel state changed + if (!same_travel_path || + last_active != is_playing || + last_current_node != current_node || + last_fading_from_node != fading_from_node || + last_fading_time != fading_time || + last_fading_pos != fading_pos) { + state_machine_draw->queue_redraw(); + last_travel_path = tp; + last_current_node = current_node; + last_active = is_playing; + last_fading_from_node = fading_from_node; + last_fading_time = fading_time; + last_fading_pos = fading_pos; + state_machine_play_pos->queue_redraw(); + } - { - if (current_node != StringName() && state_machine->has_node(current_node)) { - String next = current_node; - Ref<AnimationNodeStateMachine> anodesm = state_machine->get_node(next); - Ref<AnimationNodeStateMachinePlayback> current_node_playback; - - while (anodesm.is_valid()) { - current_node_playback = AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + next + "/playback"); - next += "/" + current_node_playback->get_current_node(); - anodesm = anodesm->get_node(current_node_playback->get_current_node()); - } + { + if (current_node != StringName() && state_machine->has_node(current_node)) { + String next = current_node; + Ref<AnimationNodeStateMachine> anodesm = state_machine->get_node(next); + Ref<AnimationNodeStateMachinePlayback> current_node_playback; - // when current_node is a state machine, use playback of current_node to set play_pos - if (current_node_playback.is_valid()) { - play_pos = current_node_playback->get_current_play_pos(); - current_length = current_node_playback->get_current_length(); + while (anodesm.is_valid()) { + current_node_playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + next + "/playback"); + next += "/" + current_node_playback->get_current_node(); + anodesm = anodesm->get_node(current_node_playback->get_current_node()); + } + + // when current_node is a state machine, use playback of current_node to set play_pos + if (current_node_playback.is_valid()) { + current_play_pos = current_node_playback->get_current_play_pos(); + current_length = current_node_playback->get_current_length(); + } } } - } - if (last_play_pos != play_pos) { - last_play_pos = play_pos; - state_machine_play_pos->update(); - } - } + if (last_play_pos != current_play_pos || fade_from_last_play_pos != fade_from_current_play_pos) { + last_play_pos = current_play_pos; + fade_from_last_play_pos = fade_from_current_play_pos; + state_machine_play_pos->queue_redraw(); + } + } break; - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - over_node = StringName(); - set_process(is_visible_in_tree()); + case NOTIFICATION_VISIBILITY_CHANGED: { + over_node = StringName(); + set_process(is_visible_in_tree()); + } break; } } @@ -1052,14 +1810,10 @@ void AnimationNodeStateMachineEditor::_open_editor(const String &p_name) { AnimationTreeEditor::get_singleton()->enter_editor(p_name); } -void AnimationNodeStateMachineEditor::_removed_from_graph() { - EditorNode::get_singleton()->edit_item(nullptr); -} - void AnimationNodeStateMachineEditor::_name_edited(const String &p_text) { const String &new_name = p_text; - ERR_FAIL_COND(new_name.is_empty() || new_name.find(".") != -1 || new_name.find("/") != -1); + ERR_FAIL_COND(new_name.is_empty() || new_name.contains(".") || new_name.contains("/")); if (new_name == prev_name) { return; // Nothing to do. @@ -1074,6 +1828,7 @@ void AnimationNodeStateMachineEditor::_name_edited(const String &p_text) { } updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Node Renamed")); undo_redo->add_do_method(state_machine.ptr(), "rename_node", prev_name, name); undo_redo->add_undo_method(state_machine.ptr(), "rename_node", name, prev_name); @@ -1083,7 +1838,7 @@ void AnimationNodeStateMachineEditor::_name_edited(const String &p_text) { name_edit_popup->hide(); updating = false; - state_machine_draw->update(); + state_machine_draw->queue_redraw(); } void AnimationNodeStateMachineEditor::_name_edited_focus_out() { @@ -1100,115 +1855,151 @@ void AnimationNodeStateMachineEditor::_scroll_changed(double) { } state_machine->set_graph_offset(Vector2(h_scroll->get_value(), v_scroll->get_value())); - state_machine_draw->update(); + state_machine_draw->queue_redraw(); } -void AnimationNodeStateMachineEditor::_erase_selected() { - if (selected_node != StringName() && state_machine->has_node(selected_node)) { - updating = true; +void AnimationNodeStateMachineEditor::_erase_selected(const bool p_nested_action) { + if (!selected_nodes.is_empty()) { + if (!p_nested_action) { + updating = true; + } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Node Removed")); - undo_redo->add_do_method(state_machine.ptr(), "remove_node", selected_node); - undo_redo->add_undo_method(state_machine.ptr(), "add_node", selected_node, state_machine->get_node(selected_node), state_machine->get_node_position(selected_node)); - for (int i = 0; i < state_machine->get_transition_count(); i++) { - String from = state_machine->get_transition_from(i); - String to = state_machine->get_transition_to(i); - if (from == selected_node || to == selected_node) { - undo_redo->add_undo_method(state_machine.ptr(), "add_transition", from, to, state_machine->get_transition(i)); + + for (int i = 0; i < node_rects.size(); i++) { + if (node_rects[i].node_name == state_machine->start_node || node_rects[i].node_name == state_machine->end_node) { + continue; + } + + if (!selected_nodes.has(node_rects[i].node_name)) { + continue; + } + + undo_redo->add_do_method(state_machine.ptr(), "remove_node", node_rects[i].node_name); + undo_redo->add_undo_method(state_machine.ptr(), "add_node", node_rects[i].node_name, + state_machine->get_node(node_rects[i].node_name), + state_machine->get_node_position(node_rects[i].node_name)); + + for (int j = 0; j < state_machine->get_transition_count(); j++) { + String from = state_machine->get_transition_from(j); + String to = state_machine->get_transition_to(j); + String local_from = from.get_slicec('/', 0); + String local_to = to.get_slicec('/', 0); + + if (local_from == node_rects[i].node_name || local_to == node_rects[i].node_name) { + undo_redo->add_undo_method(state_machine.ptr(), "add_transition", from, to, state_machine->get_transition(j)); + } } } - if (String(state_machine->get_start_node()) == selected_node) { - undo_redo->add_undo_method(state_machine.ptr(), "set_start_node", selected_node); - } + undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); undo_redo->commit_action(); - updating = false; - selected_node = StringName(); + + if (!p_nested_action) { + updating = false; + } + + selected_nodes.clear(); + } + + if (!selected_multi_transition.multi_transitions.is_empty()) { + delete_tree->clear(); + + TreeItem *root = delete_tree->create_item(); + + TreeItem *item = delete_tree->create_item(root); + item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + item->set_text(0, String(selected_transition_from) + " -> " + selected_transition_to); + item->set_editable(0, true); + + for (int i = 0; i < selected_multi_transition.multi_transitions.size(); i++) { + String from = selected_multi_transition.multi_transitions[i].from_node; + String to = selected_multi_transition.multi_transitions[i].to_node; + + item = delete_tree->create_item(root); + item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + item->set_text(0, from + " -> " + to); + item->set_editable(0, true); + } + + delete_window->popup_centered(Vector2(400, 200)); + return; } if (selected_transition_to != StringName() && selected_transition_from != StringName() && state_machine->has_transition(selected_transition_from, selected_transition_to)) { Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(state_machine->find_transition(selected_transition_from, selected_transition_to)); - updating = true; + if (!p_nested_action) { + updating = true; + } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Transition Removed")); undo_redo->add_do_method(state_machine.ptr(), "remove_transition", selected_transition_from, selected_transition_to); undo_redo->add_undo_method(state_machine.ptr(), "add_transition", selected_transition_from, selected_transition_to, tr); undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); undo_redo->commit_action(); - updating = false; + if (!p_nested_action) { + updating = false; + } selected_transition_from = StringName(); selected_transition_to = StringName(); + selected_transition_index = -1; + selected_multi_transition = TransitionLine(); } - state_machine_draw->update(); + state_machine_draw->queue_redraw(); } -void AnimationNodeStateMachineEditor::_autoplay_selected() { - if (selected_node != StringName() && state_machine->has_node(selected_node)) { - StringName new_start_node; - if (state_machine->get_start_node() == selected_node) { //toggle it - new_start_node = StringName(); +void AnimationNodeStateMachineEditor::_update_mode() { + if (tool_select->is_pressed()) { + selection_tools_hb->show(); + bool nothing_selected = selected_nodes.is_empty() && selected_transition_from == StringName() && selected_transition_to == StringName(); + bool start_end_selected = selected_nodes.size() == 1 && (*selected_nodes.begin() == state_machine->start_node || *selected_nodes.begin() == state_machine->end_node); + tool_erase->set_disabled(nothing_selected || start_end_selected || read_only); + + if (selected_nodes.is_empty() || start_end_selected || read_only) { + tool_group->set_disabled(true); + tool_group->set_visible(true); + tool_ungroup->set_visible(false); } else { - new_start_node = selected_node; - } - - updating = true; - undo_redo->create_action(TTR("Set Start Node (Autoplay)")); - undo_redo->add_do_method(state_machine.ptr(), "set_start_node", new_start_node); - undo_redo->add_undo_method(state_machine.ptr(), "set_start_node", state_machine->get_start_node()); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); - undo_redo->commit_action(); - updating = false; - state_machine_draw->update(); - } -} + Ref<AnimationNodeStateMachine> ansm = state_machine->get_node(*selected_nodes.begin()); -void AnimationNodeStateMachineEditor::_end_selected() { - if (selected_node != StringName() && state_machine->has_node(selected_node)) { - StringName new_end_node; - if (state_machine->get_end_node() == selected_node) { //toggle it - new_end_node = StringName(); - } else { - new_end_node = selected_node; + if (selected_nodes.size() == 1 && ansm.is_valid()) { + tool_group->set_disabled(true); + tool_group->set_visible(false); + tool_ungroup->set_visible(true); + } else { + tool_group->set_disabled(false); + tool_group->set_visible(true); + tool_ungroup->set_visible(false); + } } - - updating = true; - undo_redo->create_action(TTR("Set Start Node (Autoplay)")); - undo_redo->add_do_method(state_machine.ptr(), "set_end_node", new_end_node); - undo_redo->add_undo_method(state_machine.ptr(), "set_end_node", state_machine->get_end_node()); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); - undo_redo->commit_action(); - updating = false; - state_machine_draw->update(); + } else { + selection_tools_hb->hide(); } -} -void AnimationNodeStateMachineEditor::_update_mode() { - if (tool_select->is_pressed()) { - tool_erase_hb->show(); - tool_erase->set_disabled(selected_node == StringName() && selected_transition_from == StringName() && selected_transition_to == StringName()); - tool_autoplay->set_disabled(selected_node == StringName()); - tool_end->set_disabled(selected_node == StringName()); + if (tool_connect->is_pressed()) { + transition_tools_hb->show(); } else { - tool_erase_hb->hide(); + transition_tools_hb->hide(); } } void AnimationNodeStateMachineEditor::_bind_methods() { ClassDB::bind_method("_update_graph", &AnimationNodeStateMachineEditor::_update_graph); - - ClassDB::bind_method("_removed_from_graph", &AnimationNodeStateMachineEditor::_removed_from_graph); - ClassDB::bind_method("_open_editor", &AnimationNodeStateMachineEditor::_open_editor); + ClassDB::bind_method("_connect_to", &AnimationNodeStateMachineEditor::_connect_to); + ClassDB::bind_method("_stop_connecting", &AnimationNodeStateMachineEditor::_stop_connecting); + ClassDB::bind_method("_delete_selected", &AnimationNodeStateMachineEditor::_delete_selected); + ClassDB::bind_method("_delete_all", &AnimationNodeStateMachineEditor::_delete_all); + ClassDB::bind_method("_delete_tree_draw", &AnimationNodeStateMachineEditor::_delete_tree_draw); } AnimationNodeStateMachineEditor *AnimationNodeStateMachineEditor::singleton = nullptr; AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { singleton = this; - updating = false; HBoxContainer *top_hb = memnew(HBoxContainer); add_child(top_hb); @@ -1222,55 +2013,67 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { tool_select->set_toggle_mode(true); tool_select->set_button_group(bg); tool_select->set_pressed(true); - tool_select->set_tooltip(TTR("Select and move nodes.\nRMB to add new nodes.\nShift+LMB to create connections.")); - tool_select->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_update_mode), varray(), CONNECT_DEFERRED); + tool_select->set_tooltip_text(TTR("Select and move nodes.\nRMB: Add node at position clicked.\nShift+LMB+Drag: Connects the selected node with another node or creates a new node if you select an area without nodes.")); + tool_select->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_update_mode), CONNECT_DEFERRED); tool_create = memnew(Button); tool_create->set_flat(true); top_hb->add_child(tool_create); tool_create->set_toggle_mode(true); tool_create->set_button_group(bg); - tool_create->set_tooltip(TTR("Create new nodes.")); - tool_create->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_update_mode), varray(), CONNECT_DEFERRED); + tool_create->set_tooltip_text(TTR("Create new nodes.")); + tool_create->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_update_mode), CONNECT_DEFERRED); tool_connect = memnew(Button); tool_connect->set_flat(true); top_hb->add_child(tool_connect); tool_connect->set_toggle_mode(true); tool_connect->set_button_group(bg); - tool_connect->set_tooltip(TTR("Connect nodes.")); - tool_connect->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_update_mode), varray(), CONNECT_DEFERRED); + tool_connect->set_tooltip_text(TTR("Connect nodes.")); + tool_connect->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_update_mode), CONNECT_DEFERRED); + + // Context-sensitive selection tools: + selection_tools_hb = memnew(HBoxContainer); + top_hb->add_child(selection_tools_hb); + selection_tools_hb->add_child(memnew(VSeparator)); + + tool_group = memnew(Button); + tool_group->set_flat(true); + tool_group->set_tooltip_text(TTR("Group Selected Node(s)") + " (Ctrl+G)"); + tool_group->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_group_selected_nodes)); + tool_group->set_disabled(true); + selection_tools_hb->add_child(tool_group); + + tool_ungroup = memnew(Button); + tool_ungroup->set_flat(true); + tool_ungroup->set_tooltip_text(TTR("Ungroup Selected Node") + " (Ctrl+Shift+G)"); + tool_ungroup->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_ungroup_selected_nodes)); + tool_ungroup->set_visible(false); + selection_tools_hb->add_child(tool_ungroup); - tool_erase_hb = memnew(HBoxContainer); - top_hb->add_child(tool_erase_hb); - tool_erase_hb->add_child(memnew(VSeparator)); tool_erase = memnew(Button); tool_erase->set_flat(true); - tool_erase->set_tooltip(TTR("Remove selected node or transition.")); - tool_erase_hb->add_child(tool_erase); - tool_erase->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_erase_selected)); + tool_erase->set_tooltip_text(TTR("Remove selected node or transition.")); + tool_erase->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_erase_selected).bind(false)); tool_erase->set_disabled(true); + selection_tools_hb->add_child(tool_erase); - tool_erase_hb->add_child(memnew(VSeparator)); + transition_tools_hb = memnew(HBoxContainer); + top_hb->add_child(transition_tools_hb); + transition_tools_hb->add_child(memnew(VSeparator)); - tool_autoplay = memnew(Button); - tool_autoplay->set_flat(true); - tool_autoplay->set_tooltip(TTR("Toggle autoplay this animation on start, restart or seek to zero.")); - tool_erase_hb->add_child(tool_autoplay); - tool_autoplay->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_autoplay_selected)); - tool_autoplay->set_disabled(true); + transition_tools_hb->add_child(memnew(Label(TTR("Transition:")))); + switch_mode = memnew(OptionButton); + transition_tools_hb->add_child(switch_mode); - tool_end = memnew(Button); - tool_end->set_flat(true); - tool_end->set_tooltip(TTR("Set the end animation. This is useful for sub-transitions.")); - tool_erase_hb->add_child(tool_end); - tool_end->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_end_selected)); - tool_end->set_disabled(true); + auto_advance = memnew(Button); + auto_advance->set_flat(true); + auto_advance->set_tooltip_text(TTR("New Transitions Should Auto Advance")); + auto_advance->set_toggle_mode(true); + auto_advance->set_pressed(true); + transition_tools_hb->add_child(auto_advance); - top_hb->add_child(memnew(VSeparator)); - top_hb->add_child(memnew(Label(TTR("Transition: ")))); - transition_mode = memnew(OptionButton); - top_hb->add_child(transition_mode); + // top_hb->add_spacer(); @@ -1280,6 +2083,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { panel = memnew(PanelContainer); panel->set_clip_contents(true); + panel->set_mouse_filter(Control::MOUSE_FILTER_PASS); add_child(panel); panel->set_v_size_flags(SIZE_EXPAND_FILL); @@ -1288,12 +2092,13 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { state_machine_draw->connect("gui_input", callable_mp(this, &AnimationNodeStateMachineEditor::_state_machine_gui_input)); state_machine_draw->connect("draw", callable_mp(this, &AnimationNodeStateMachineEditor::_state_machine_draw)); state_machine_draw->set_focus_mode(FOCUS_ALL); + state_machine_draw->set_mouse_filter(Control::MOUSE_FILTER_PASS); state_machine_play_pos = memnew(Control); state_machine_draw->add_child(state_machine_play_pos); state_machine_play_pos->set_mouse_filter(MOUSE_FILTER_PASS); //pass all to parent - state_machine_play_pos->set_anchors_and_offsets_preset(PRESET_WIDE); - state_machine_play_pos->connect("draw", callable_mp(this, &AnimationNodeStateMachineEditor::_state_machine_pos_draw)); + state_machine_play_pos->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + state_machine_play_pos->connect("draw", callable_mp(this, &AnimationNodeStateMachineEditor::_state_machine_pos_draw_all)); v_scroll = memnew(VScrollBar); state_machine_draw->add_child(v_scroll); @@ -1312,24 +2117,38 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { error_panel->add_child(error_label); error_panel->hide(); - undo_redo = EditorNode::get_undo_redo(); - set_custom_minimum_size(Size2(0, 300 * EDSCALE)); menu = memnew(PopupMenu); add_child(menu); menu->connect("id_pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_add_menu_type)); + menu->connect("popup_hide", callable_mp(this, &AnimationNodeStateMachineEditor::_stop_connecting)); animations_menu = memnew(PopupMenu); menu->add_child(animations_menu); animations_menu->set_name("animations"); animations_menu->connect("index_pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_add_animation_type)); + connect_menu = memnew(PopupMenu); + add_child(connect_menu); + connect_menu->connect("id_pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_connect_to)); + connect_menu->connect("popup_hide", callable_mp(this, &AnimationNodeStateMachineEditor::_stop_connecting)); + + state_machine_menu = memnew(PopupMenu); + state_machine_menu->set_name("state_machines"); + state_machine_menu->connect("id_pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_connect_to)); + connect_menu->add_child(state_machine_menu); + + end_menu = memnew(PopupMenu); + end_menu->set_name("end_nodes"); + end_menu->connect("id_pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_connect_to)); + connect_menu->add_child(end_menu); + name_edit_popup = memnew(Popup); add_child(name_edit_popup); name_edit = memnew(LineEdit); name_edit_popup->add_child(name_edit); - name_edit->set_anchors_and_offsets_preset(PRESET_WIDE); + name_edit->set_anchors_and_offsets_preset(PRESET_FULL_RECT); name_edit->connect("text_submitted", callable_mp(this, &AnimationNodeStateMachineEditor::_name_edited)); name_edit->connect("focus_exited", callable_mp(this, &AnimationNodeStateMachineEditor::_name_edited_focus_out)); @@ -1338,15 +2157,95 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { open_file->set_title(TTR("Open Animation Node")); open_file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); open_file->connect("file_selected", callable_mp(this, &AnimationNodeStateMachineEditor::_file_opened)); - undo_redo = EditorNode::get_undo_redo(); - over_text = false; + delete_window = memnew(ConfirmationDialog); + delete_window->set_flag(Window::FLAG_RESIZE_DISABLED, true); + add_child(delete_window); + + delete_tree = memnew(Tree); + delete_tree->set_hide_root(true); + delete_tree->connect("draw", callable_mp(this, &AnimationNodeStateMachineEditor::_delete_tree_draw)); + delete_window->add_child(delete_tree); + + Button *ok = delete_window->get_cancel_button(); + ok->set_text(TTR("Delete Selected")); + ok->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_delete_selected)); + + Button *delete_all = delete_window->add_button(TTR("Delete All"), true); + delete_all->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_delete_all)); over_node_what = -1; dragging_selected_attempt = false; connecting = false; + selected_transition_index = -1; last_active = false; error_time = 0; } + +void EditorAnimationMultiTransitionEdit::add_transition(const StringName &p_from, const StringName &p_to, Ref<AnimationNodeStateMachineTransition> p_transition) { + Transition tr; + tr.from = p_from; + tr.to = p_to; + tr.transition = p_transition; + transitions.push_back(tr); +} + +bool EditorAnimationMultiTransitionEdit::_set(const StringName &p_name, const Variant &p_property) { + int index = String(p_name).get_slicec('/', 0).to_int(); + StringName prop = String(p_name).get_slicec('/', 1); + + bool found; + transitions.write[index].transition->set(prop, p_property, &found); + if (found) { + return true; + } + + return false; +} + +bool EditorAnimationMultiTransitionEdit::_get(const StringName &p_name, Variant &r_property) const { + int index = String(p_name).get_slicec('/', 0).to_int(); + StringName prop = String(p_name).get_slicec('/', 1); + + if (prop == "transition_path") { + r_property = String(transitions[index].from) + " -> " + transitions[index].to; + return true; + } + + bool found; + r_property = transitions[index].transition->get(prop, &found); + if (found) { + return true; + } + + return false; +} + +void EditorAnimationMultiTransitionEdit::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < transitions.size(); i++) { + List<PropertyInfo> plist; + transitions[i].transition->get_property_list(&plist, true); + + PropertyInfo prop_transition_path; + prop_transition_path.type = Variant::STRING; + prop_transition_path.name = itos(i) + "/" + "transition_path"; + p_list->push_back(prop_transition_path); + + for (List<PropertyInfo>::Element *F = plist.front(); F; F = F->next()) { + if (F->get().name == "script" || F->get().name == "resource_name" || F->get().name == "resource_path" || F->get().name == "resource_local_to_scene") { + continue; + } + + if (F->get().usage != PROPERTY_USAGE_DEFAULT) { + continue; + } + + PropertyInfo prop = F->get(); + prop.name = itos(i) + "/" + prop.name; + + p_list->push_back(prop); + } + } +} diff --git a/editor/plugins/animation_state_machine_editor.h b/editor/plugins/animation_state_machine_editor.h index 8970e3e062..66338c820e 100644 --- a/editor/plugins/animation_state_machine_editor.h +++ b/editor/plugins/animation_state_machine_editor.h @@ -1,113 +1,134 @@ -/*************************************************************************/ -/* animation_state_machine_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_state_machine_editor.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 ANIMATION_STATE_MACHINE_EDITOR_H #define ANIMATION_STATE_MACHINE_EDITOR_H -#include "editor/editor_node.h" -#include "editor/editor_plugin.h" #include "editor/plugins/animation_tree_editor_plugin.h" -#include "editor/property_editor.h" #include "scene/animation/animation_node_state_machine.h" -#include "scene/gui/button.h" #include "scene/gui/graph_edit.h" #include "scene/gui/popup.h" #include "scene/gui/tree.h" +class ConfirmationDialog; +class EditorFileDialog; +class OptionButton; +class PanelContainer; + class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeStateMachineEditor, AnimationTreeNodeEditorPlugin); Ref<AnimationNodeStateMachine> state_machine; - Button *tool_select; - Button *tool_create; - Button *tool_connect; - Popup *name_edit_popup; - LineEdit *name_edit; + bool read_only = false; - HBoxContainer *tool_erase_hb; - Button *tool_erase; - Button *tool_autoplay; - Button *tool_end; + Button *tool_select = nullptr; + Button *tool_create = nullptr; + Button *tool_connect = nullptr; + Popup *name_edit_popup = nullptr; + LineEdit *name_edit = nullptr; - OptionButton *transition_mode; - OptionButton *play_mode; + HBoxContainer *selection_tools_hb = nullptr; + Button *tool_group = nullptr; + Button *tool_ungroup = nullptr; + Button *tool_erase = nullptr; - PanelContainer *panel; + HBoxContainer *transition_tools_hb = nullptr; + OptionButton *switch_mode = nullptr; + Button *auto_advance = nullptr; - StringName selected_node; + OptionButton *play_mode = nullptr; - HScrollBar *h_scroll; - VScrollBar *v_scroll; + PanelContainer *panel = nullptr; + + StringName selected_node; + HashSet<StringName> selected_nodes; - Control *state_machine_draw; - Control *state_machine_play_pos; + HScrollBar *h_scroll = nullptr; + VScrollBar *v_scroll = nullptr; - PanelContainer *error_panel; - Label *error_label; + Control *state_machine_draw = nullptr; + Control *state_machine_play_pos = nullptr; - bool updating; + PanelContainer *error_panel = nullptr; + Label *error_label = nullptr; - UndoRedo *undo_redo; + bool updating = false; static AnimationNodeStateMachineEditor *singleton; void _state_machine_gui_input(const Ref<InputEvent> &p_event); - void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance); + void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_multi_transitions); + void _state_machine_draw(); - void _state_machine_pos_draw(); + + void _state_machine_pos_draw_individual(String p_name, float p_ratio); + void _state_machine_pos_draw_all(); void _update_graph(); - PopupMenu *menu; - PopupMenu *animations_menu; + PopupMenu *menu = nullptr; + PopupMenu *connect_menu = nullptr; + PopupMenu *state_machine_menu = nullptr; + PopupMenu *end_menu = nullptr; + PopupMenu *animations_menu = nullptr; Vector<String> animations_to_add; + Vector<String> nodes_to_connect; Vector2 add_node_pos; - bool dragging_selected_attempt; - bool dragging_selected; + ConfirmationDialog *delete_window = nullptr; + Tree *delete_tree = nullptr; + + bool box_selecting = false; + Point2 box_selecting_from; + Point2 box_selecting_to; + Rect2 box_selecting_rect; + HashSet<StringName> previous_selected; + + bool dragging_selected_attempt = false; + bool dragging_selected = false; Vector2 drag_from; Vector2 drag_ofs; StringName snap_x; StringName snap_y; - bool connecting; + bool connecting = false; + bool connection_follows_cursor = false; StringName connecting_from; Vector2 connecting_to; StringName connecting_to_node; void _add_menu_type(int p_index); void _add_animation_type(int p_index); - - void _removed_from_graph(); + void _connect_to(int p_index); struct NodeRect { StringName node_name; @@ -130,16 +151,38 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { bool disabled = false; bool auto_advance = false; float width = 0; + bool selected; + bool travel; + float fade_ratio; + bool hidden; + int transition_index; + Vector<TransitionLine> multi_transitions; }; Vector<TransitionLine> transition_lines; + struct NodeUR { + StringName name; + Ref<AnimationNode> node; + Vector2 position; + }; + + struct TransitionUR { + StringName new_from; + StringName new_to; + StringName old_from; + StringName old_to; + Ref<AnimationNodeStateMachineTransition> transition; + }; + StringName selected_transition_from; StringName selected_transition_to; + int selected_transition_index; + TransitionLine selected_multi_transition; + void _add_transition(const bool p_nested_action = false); - bool over_text; StringName over_node; - int over_node_what; + int over_node_what = -1; String prev_name; void _name_edited(const String &p_text); @@ -147,26 +190,45 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { void _open_editor(const String &p_name); void _scroll_changed(double); - void _clip_src_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect); - void _clip_dst_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect); + void _clip_src_line_to_rect(Vector2 &r_from, const Vector2 &p_to, const Rect2 &p_rect); + void _clip_dst_line_to_rect(const Vector2 &p_from, Vector2 &r_to, const Rect2 &p_rect); - void _erase_selected(); + void _erase_selected(const bool p_nested_action = false); void _update_mode(); - void _autoplay_selected(); - void _end_selected(); + void _open_menu(const Vector2 &p_position); + void _open_connect_menu(const Vector2 &p_position); + bool _create_submenu(PopupMenu *p_menu, Ref<AnimationNodeStateMachine> p_nodesm, const StringName &p_name, const StringName &p_path, bool from_root = false, Vector<Ref<AnimationNodeStateMachine>> p_parents = Vector<Ref<AnimationNodeStateMachine>>()); + void _stop_connecting(); + + void _group_selected_nodes(); + void _ungroup_selected_nodes(); - bool last_active; - StringName last_blend_from_node; + void _delete_selected(); + void _delete_all(); + void _delete_tree_draw(); + + bool last_active = false; + StringName last_fading_from_node; StringName last_current_node; Vector<StringName> last_travel_path; - float last_play_pos; - float play_pos; - float current_length; - float error_time; + float fade_from_last_play_pos = 0.0f; + float fade_from_current_play_pos = 0.0f; + float fade_from_length = 0.0f; + + float last_play_pos = 0.0f; + float current_play_pos = 0.0f; + float current_length = 0.0f; + + float last_fading_time = 0.0f; + float last_fading_pos = 0.0f; + float fading_time = 0.0f; + float fading_pos = 0.0f; + + float error_time = 0.0f; String error_text; - EditorFileDialog *open_file; + EditorFileDialog *open_file = nullptr; Ref<AnimationNode> file_loaded; void _file_opened(const String &p_file); @@ -184,7 +246,30 @@ public: static AnimationNodeStateMachineEditor *get_singleton() { return singleton; } virtual bool can_edit(const Ref<AnimationNode> &p_node) override; virtual void edit(const Ref<AnimationNode> &p_node) override; + virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override; AnimationNodeStateMachineEditor(); }; +class EditorAnimationMultiTransitionEdit : public RefCounted { + GDCLASS(EditorAnimationMultiTransitionEdit, RefCounted); + + struct Transition { + StringName from; + StringName to; + Ref<AnimationNodeStateMachineTransition> transition; + }; + + Vector<Transition> transitions; + +protected: + bool _set(const StringName &p_name, const Variant &p_property); + bool _get(const StringName &p_name, Variant &r_property) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + void add_transition(const StringName &p_from, const StringName &p_to, Ref<AnimationNodeStateMachineTransition> p_transition); + + EditorAnimationMultiTransitionEdit(){}; +}; + #endif // ANIMATION_STATE_MACHINE_EDITOR_H diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp index adfea236d3..ab46e8f04a 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* animation_tree_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_tree_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 "animation_tree_editor_plugin.h" @@ -39,6 +39,8 @@ #include "core/io/resource_loader.h" #include "core/math/delaunay_2d.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "scene/animation/animation_blend_tree.h" #include "scene/animation/animation_player.h" @@ -48,18 +50,35 @@ #include "scene/scene_string_names.h" void AnimationTreeEditor::edit(AnimationTree *p_tree) { + if (p_tree && !p_tree->is_connected("animation_player_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) { + p_tree->connect("animation_player_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed), CONNECT_DEFERRED); + } + if (tree == p_tree) { return; } + if (tree && tree->is_connected("animation_player_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) { + tree->disconnect("animation_player_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed)); + } + tree = p_tree; Vector<String> path; - if (tree && tree->has_meta("_tree_edit_path")) { - path = tree->get_meta("_tree_edit_path"); + if (tree) { + if (tree->has_meta("_tree_edit_path")) { + path = tree->get_meta("_tree_edit_path"); + } else { + current_root = ObjectID(); + } edit_path(path); - } else { - current_root = ObjectID(); + } +} + +void AnimationTreeEditor::_node_removed(Node *p_node) { + if (p_node == tree) { + tree = nullptr; + _clear_editors(); } } @@ -70,6 +89,13 @@ void AnimationTreeEditor::_path_button_pressed(int p_path) { } } +void AnimationTreeEditor::_animation_list_changed() { + AnimationNodeBlendTreeEditor *bte = AnimationNodeBlendTreeEditor::get_singleton(); + if (bte) { + bte->update_graph(); + } +} + void AnimationTreeEditor::_update_path() { while (path_hb->get_child_count() > 1) { memdelete(path_hb->get_child(1)); @@ -84,7 +110,7 @@ void AnimationTreeEditor::_update_path() { b->set_button_group(group); b->set_pressed(true); b->set_focus_mode(FOCUS_NONE); - b->connect("pressed", callable_mp(this, &AnimationTreeEditor::_path_button_pressed), varray(-1)); + b->connect("pressed", callable_mp(this, &AnimationTreeEditor::_path_button_pressed).bind(-1)); path_hb->add_child(b); for (int i = 0; i < button_path.size(); i++) { b = memnew(Button); @@ -94,7 +120,7 @@ void AnimationTreeEditor::_update_path() { path_hb->add_child(b); b->set_pressed(true); b->set_focus_mode(FOCUS_NONE); - b->connect("pressed", callable_mp(this, &AnimationTreeEditor::_path_button_pressed), varray(i)); + b->connect("pressed", callable_mp(this, &AnimationTreeEditor::_path_button_pressed).bind(i)); } } @@ -127,11 +153,26 @@ void AnimationTreeEditor::edit_path(const Vector<String> &p_path) { } else { current_root = ObjectID(); edited_path = button_path; + for (int i = 0; i < editors.size(); i++) { + editors[i]->edit(Ref<AnimationNode>()); + editors[i]->hide(); + } } _update_path(); } +void AnimationTreeEditor::_clear_editors() { + button_path.clear(); + current_root = ObjectID(); + edited_path = button_path; + for (int i = 0; i < editors.size(); i++) { + editors[i]->edit(Ref<AnimationNode>()); + editors[i]->hide(); + } + _update_path(); +} + Vector<String> AnimationTreeEditor::get_edited_path() const { return button_path; } @@ -143,19 +184,27 @@ void AnimationTreeEditor::enter_editor(const String &p_path) { } void AnimationTreeEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_PROCESS) { - ObjectID root; - if (tree && tree->get_tree_root().is_valid()) { - root = tree->get_tree_root()->get_instance_id(); - } + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", callable_mp(this, &AnimationTreeEditor::_node_removed)); + } break; + case NOTIFICATION_PROCESS: { + ObjectID root; + if (tree && tree->get_tree_root().is_valid()) { + root = tree->get_tree_root()->get_instance_id(); + } - if (root != current_root) { - edit_path(Vector<String>()); - } + if (root != current_root) { + edit_path(Vector<String>()); + } - if (button_path.size() != edited_path.size()) { - edit_path(edited_path); - } + if (button_path.size() != edited_path.size()) { + edit_path(edited_path); + } + } break; + case NOTIFICATION_EXIT_TREE: { + get_tree()->disconnect("node_removed", callable_mp(this, &AnimationTreeEditor::_node_removed)); + } break; } } @@ -257,23 +306,22 @@ void AnimationTreeEditorPlugin::make_visible(bool p_visible) { //editor->hide_animation_player_editors(); //editor->animation_panel_make_visible(true); button->show(); - editor->make_bottom_panel_item_visible(anim_tree_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(anim_tree_editor); anim_tree_editor->set_process(true); } else { if (anim_tree_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); } button->hide(); anim_tree_editor->set_process(false); } } -AnimationTreeEditorPlugin::AnimationTreeEditorPlugin(EditorNode *p_node) { - editor = p_node; +AnimationTreeEditorPlugin::AnimationTreeEditorPlugin() { anim_tree_editor = memnew(AnimationTreeEditor); anim_tree_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE); - button = editor->add_bottom_panel_item(TTR("AnimationTree"), anim_tree_editor); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("AnimationTree"), anim_tree_editor); button->hide(); } diff --git a/editor/plugins/animation_tree_editor_plugin.h b/editor/plugins/animation_tree_editor_plugin.h index 14c5658478..ac937000cb 100644 --- a/editor/plugins/animation_tree_editor_plugin.h +++ b/editor/plugins/animation_tree_editor_plugin.h @@ -1,44 +1,42 @@ -/*************************************************************************/ -/* animation_tree_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_tree_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 ANIMATION_TREE_EDITOR_PLUGIN_H #define ANIMATION_TREE_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" -#include "editor/property_editor.h" #include "scene/animation/animation_tree.h" #include "scene/gui/button.h" #include "scene/gui/graph_edit.h" -#include "scene/gui/popup.h" -#include "scene/gui/tree.h" + +class EditorFileDialog; class AnimationTreeNodeEditorPlugin : public VBoxContainer { GDCLASS(AnimationTreeNodeEditorPlugin, VBoxContainer); @@ -51,31 +49,34 @@ public: class AnimationTreeEditor : public VBoxContainer { GDCLASS(AnimationTreeEditor, VBoxContainer); - ScrollContainer *path_edit; - HBoxContainer *path_hb; + ScrollContainer *path_edit = nullptr; + HBoxContainer *path_hb = nullptr; - AnimationTree *tree; - MarginContainer *editor_base; + AnimationTree *tree = nullptr; + MarginContainer *editor_base = nullptr; Vector<String> button_path; Vector<String> edited_path; Vector<AnimationTreeNodeEditorPlugin *> editors; void _update_path(); + void _clear_editors(); ObjectID current_root; void _path_button_pressed(int p_path); + void _animation_list_changed(); static Vector<String> get_animation_list(); protected: void _notification(int p_what); + void _node_removed(Node *p_node); static void _bind_methods(); static AnimationTreeEditor *singleton; public: - AnimationTree *get_tree() { return tree; } + AnimationTree *get_animation_tree() { return tree; } void add_plugin(AnimationTreeNodeEditorPlugin *p_editor); void remove_plugin(AnimationTreeNodeEditorPlugin *p_editor); @@ -95,9 +96,8 @@ public: class AnimationTreeEditorPlugin : public EditorPlugin { GDCLASS(AnimationTreeEditorPlugin, EditorPlugin); - AnimationTreeEditor *anim_tree_editor; - EditorNode *editor; - Button *button; + AnimationTreeEditor *anim_tree_editor = nullptr; + Button *button = nullptr; public: virtual String get_name() const override { return "AnimationTree"; } @@ -106,7 +106,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - AnimationTreeEditorPlugin(EditorNode *p_node); + AnimationTreeEditorPlugin(); ~AnimationTreeEditorPlugin(); }; diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index 4b7ad7e325..2639765283 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -1,49 +1,58 @@ -/*************************************************************************/ -/* asset_library_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* asset_library_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 "asset_library_editor_plugin.h" #include "core/input/input.h" #include "core/io/json.h" +#include "core/io/stream_peer_tls.h" #include "core/os/keyboard.h" #include "core/version.h" +#include "editor/editor_file_dialog.h" #include "editor/editor_node.h" +#include "editor/editor_paths.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/project_settings_editor.h" +#include "scene/gui/menu_button.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#ifdef MODULE_SVG_ENABLED +#include "modules/svg/image_loader_svg.h" +#endif static inline void setup_http_request(HTTPRequest *request) { request->set_use_threads(EDITOR_DEF("asset_library/use_threads", true)); - const String proxy_host = EDITOR_DEF("network/http_proxy/host", ""); - const int proxy_port = EDITOR_DEF("network/http_proxy/port", -1); + const String proxy_host = EDITOR_GET("network/http_proxy/host"); + const int proxy_port = EDITOR_GET("network/http_proxy/port"); request->set_http_proxy(proxy_host, proxy_port); request->set_https_proxy(proxy_host, proxy_port); } @@ -62,15 +71,17 @@ void EditorAssetLibraryItem::set_image(int p_type, int p_index, const Ref<Textur ERR_FAIL_COND(p_type != EditorAssetLibrary::IMAGE_QUEUE_ICON); ERR_FAIL_COND(p_index != 0); - icon->set_normal_texture(p_image); + icon->set_texture_normal(p_image); } void EditorAssetLibraryItem::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - icon->set_normal_texture(get_theme_icon(SNAME("ProjectIconLoading"), SNAME("EditorIcons"))); - category->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); - author->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); - price->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + icon->set_texture_normal(get_theme_icon(SNAME("ProjectIconLoading"), SNAME("EditorIcons"))); + category->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); + author->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); + price->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); + } break; } } @@ -96,10 +107,7 @@ void EditorAssetLibraryItem::_bind_methods() { EditorAssetLibraryItem::EditorAssetLibraryItem() { Ref<StyleBoxEmpty> border; border.instantiate(); - border->set_default_margin(SIDE_LEFT, 5 * EDSCALE); - border->set_default_margin(SIDE_RIGHT, 5 * EDSCALE); - border->set_default_margin(SIDE_BOTTOM, 5 * EDSCALE); - border->set_default_margin(SIDE_TOP, 5 * EDSCALE); + border->set_content_margin_all(5 * EDSCALE); add_theme_style_override("panel", border); HBoxContainer *hb = memnew(HBoxContainer); @@ -156,18 +164,13 @@ void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const Ref<Image> overlay = previews->get_theme_icon(SNAME("PlayOverlay"), SNAME("EditorIcons"))->get_image(); Ref<Image> thumbnail = p_image->get_image(); thumbnail = thumbnail->duplicate(); - Point2 overlay_pos = Point2((thumbnail->get_width() - overlay->get_width()) / 2, (thumbnail->get_height() - overlay->get_height()) / 2); + Point2i overlay_pos = Point2i((thumbnail->get_width() - overlay->get_width()) / 2, (thumbnail->get_height() - overlay->get_height()) / 2); // Overlay and thumbnail need the same format for `blend_rect` to work. thumbnail->convert(Image::FORMAT_RGBA8); - thumbnail->blend_rect(overlay, overlay->get_used_rect(), overlay_pos); + preview_images[i].button->set_icon(ImageTexture::create_from_image(thumbnail)); - Ref<ImageTexture> tex; - tex.instantiate(); - tex->create_from_image(thumbnail); - - preview_images[i].button->set_icon(tex); // Make it clearer that clicking it will open an external link preview_images[i].button->set_default_cursor_shape(Control::CURSOR_POINTING_HAND); } else { @@ -193,7 +196,8 @@ void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const void EditorAssetLibraryItemDescription::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { previews_bg->add_theme_style_override("panel", previews->get_theme_stylebox(SNAME("normal"), SNAME("TextEdit"))); } break; } @@ -245,19 +249,19 @@ void EditorAssetLibraryItemDescription::configure(const String &p_title, int p_a } void EditorAssetLibraryItemDescription::add_preview(int p_id, bool p_video, const String &p_url) { - Preview preview; - preview.id = p_id; - preview.video_link = p_url; - preview.is_video = p_video; - preview.button = memnew(Button); - preview.button->set_icon(previews->get_theme_icon(SNAME("ThumbnailWait"), SNAME("EditorIcons"))); - preview.button->set_toggle_mode(true); - preview.button->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDescription::_preview_click), varray(p_id)); - preview_hb->add_child(preview.button); + Preview new_preview; + new_preview.id = p_id; + new_preview.video_link = p_url; + new_preview.is_video = p_video; + new_preview.button = memnew(Button); + new_preview.button->set_icon(previews->get_theme_icon(SNAME("ThumbnailWait"), SNAME("EditorIcons"))); + new_preview.button->set_toggle_mode(true); + new_preview.button->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDescription::_preview_click).bind(p_id)); + preview_hb->add_child(new_preview.button); if (!p_video) { - preview.image = previews->get_theme_icon(SNAME("ThumbnailWait"), SNAME("EditorIcons")); + new_preview.image = previews->get_theme_icon(SNAME("ThumbnailWait"), SNAME("EditorIcons")); } - preview_images.push_back(preview); + preview_images.push_back(new_preview); if (preview_images.size() == 1 && !p_video) { _preview_click(p_id); } @@ -285,12 +289,15 @@ EditorAssetLibraryItemDescription::EditorAssetLibraryItemDescription() { hbox->add_child(previews_vbox); previews_vbox->add_theme_constant_override("separation", 15 * EDSCALE); previews_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL); + previews_vbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); preview = memnew(TextureRect); previews_vbox->add_child(preview); - preview->set_expand(true); + preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); preview->set_custom_minimum_size(Size2(640 * EDSCALE, 345 * EDSCALE)); + preview->set_v_size_flags(Control::SIZE_EXPAND_FILL); + preview->set_h_size_flags(Control::SIZE_EXPAND_FILL); previews_bg = memnew(PanelContainer); previews_vbox->add_child(previews_bg); @@ -303,8 +310,8 @@ EditorAssetLibraryItemDescription::EditorAssetLibraryItemDescription() { preview_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL); previews->add_child(preview_hb); - get_ok_button()->set_text(TTR("Download")); - get_cancel_button()->set_text(TTR("Close")); + set_ok_button_text(TTR("Download")); + set_cancel_button_text(TTR("Close")); } /////////////////////////////////////////////////////////////////////////////////// @@ -320,7 +327,7 @@ void EditorAssetLibraryItemDownload::_http_download_completed(int p_status, int status->set_text(TTR("Can't connect.")); } break; case HTTPRequest::RESULT_CANT_CONNECT: - case HTTPRequest::RESULT_SSL_HANDSHAKE_ERROR: { + case HTTPRequest::RESULT_TLS_HANDSHAKE_ERROR: { error_text = TTR("Can't connect to host:") + " " + host; status->set_text(TTR("Can't connect.")); } break; @@ -368,19 +375,19 @@ void EditorAssetLibraryItemDownload::_http_download_completed(int p_status, int download_error->set_text(TTR("Asset Download Error:") + "\n" + error_text); download_error->popup_centered(); // Let the user retry the download. - retry->show(); + retry_button->show(); return; } - install->set_disabled(false); - status->set_text(TTR("Success!")); + install_button->set_disabled(false); + status->set_text(TTR("Ready to install!")); // Make the progress bar invisible but don't reflow other Controls around it. progress->set_modulate(Color(0, 0, 0, 0)); set_process(false); // Automatically prompt for installation once the download is completed. - _install(); + install(); } void EditorAssetLibraryItemDownload::configure(const String &p_title, int p_asset_id, const Ref<Texture2D> &p_preview, const String &p_download_url, const String &p_sha256_hash) { @@ -397,11 +404,13 @@ void EditorAssetLibraryItemDownload::configure(const String &p_title, int p_asse void EditorAssetLibraryItemDownload::_notification(int p_what) { switch (p_what) { - // FIXME: The editor crashes if 'NOTICATION_THEME_CHANGED' is used. - case NOTIFICATION_ENTER_TREE: { - add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); - dismiss->set_normal_texture(get_theme_icon(SNAME("Close"), SNAME("EditorIcons"))); + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("AssetLib"))); + status->add_theme_color_override("font_color", get_theme_color(SNAME("status_color"), SNAME("AssetLib"))); + dismiss_button->set_texture_normal(get_theme_icon(SNAME("dismiss"), SNAME("AssetLib"))); } break; + case NOTIFICATION_PROCESS: { // Make the progress bar visible again when retrying the download. progress->set_modulate(Color(1, 1, 1, 1)); @@ -457,10 +466,14 @@ void EditorAssetLibraryItemDownload::_notification(int p_what) { void EditorAssetLibraryItemDownload::_close() { // Clean up downloaded file. DirAccess::remove_file_or_error(download->get_download_file()); - queue_delete(); + queue_free(); +} + +bool EditorAssetLibraryItemDownload::can_install() const { + return !install_button->is_disabled(); } -void EditorAssetLibraryItemDownload::_install() { +void EditorAssetLibraryItemDownload::install() { String file = download->get_download_file(); if (external_install) { @@ -474,10 +487,10 @@ void EditorAssetLibraryItemDownload::_install() { void EditorAssetLibraryItemDownload::_make_request() { // Hide the Retry button if we've just pressed it. - retry->hide(); + retry_button->hide(); download->cancel_request(); - download->set_download_file(EditorPaths::get_singleton()->get_cache_dir().plus_file("tmp_asset_" + itos(asset_id)) + ".zip"); + download->set_download_file(EditorPaths::get_singleton()->get_cache_dir().path_join("tmp_asset_" + itos(asset_id)) + ".zip"); Error err = download->request(host); if (err != OK) { @@ -492,9 +505,14 @@ void EditorAssetLibraryItemDownload::_bind_methods() { } EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { + panel = memnew(PanelContainer); + add_child(panel); + HBoxContainer *hb = memnew(HBoxContainer); - add_child(hb); + panel->add_child(hb); icon = memnew(TextureRect); + icon->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + icon->set_v_size_flags(0); hb->add_child(icon); VBoxContainer *vb = memnew(VBoxContainer); @@ -507,9 +525,9 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { title_hb->add_child(title); title->set_h_size_flags(Control::SIZE_EXPAND_FILL); - dismiss = memnew(TextureButton); - dismiss->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDownload::_close)); - title_hb->add_child(dismiss); + dismiss_button = memnew(TextureButton); + dismiss_button->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDownload::_close)); + title_hb->add_child(dismiss_button); title->set_clip_text(true); @@ -517,7 +535,6 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { status = memnew(Label(TTR("Idle"))); vb->add_child(status); - status->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); progress = memnew(ProgressBar); vb->add_child(progress); @@ -525,32 +542,32 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { vb->add_child(hb2); hb2->add_spacer(); - install = memnew(Button); - install->set_text(TTR("Install...")); - install->set_disabled(true); - install->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDownload::_install)); + install_button = memnew(Button); + install_button->set_text(TTR("Install...")); + install_button->set_disabled(true); + install_button->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDownload::install)); - retry = memnew(Button); - retry->set_text(TTR("Retry")); - retry->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDownload::_make_request)); + retry_button = memnew(Button); + retry_button->set_text(TTR("Retry")); + retry_button->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDownload::_make_request)); // Only show the Retry button in case of a failure. - retry->hide(); + retry_button->hide(); - hb2->add_child(retry); - hb2->add_child(install); + hb2->add_child(retry_button); + hb2->add_child(install_button); set_custom_minimum_size(Size2(310, 0) * EDSCALE); download = memnew(HTTPRequest); - add_child(download); + panel->add_child(download); download->connect("request_completed", callable_mp(this, &EditorAssetLibraryItemDownload::_http_download_completed)); setup_http_request(download); download_error = memnew(AcceptDialog); - add_child(download_error); + panel->add_child(download_error); download_error->set_title(TTR("Download Error")); asset_installer = memnew(EditorAssetInstaller); - add_child(asset_installer); + panel->add_child(asset_installer); asset_installer->connect("confirmed", callable_mp(this, &EditorAssetLibraryItemDownload::_close)); prev_status = -1; @@ -562,24 +579,34 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { void EditorAssetLibrary::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { + add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("AssetLib"))); + error_label->move_to_front(); + } break; + + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { error_tr->set_texture(get_theme_icon(SNAME("Error"), SNAME("EditorIcons"))); filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - filter->set_clear_button_enabled(true); - - error_label->raise(); + library_scroll_bg->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + downloads_scroll->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + error_label->add_theme_color_override("color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible()) { +#ifndef ANDROID_ENABLED // Focus the search box automatically when switching to the Templates tab (in the Project Manager) // or switching to the AssetLib tab (in the editor). // The Project Manager's project filter box is automatically focused in the project manager code. filter->grab_focus(); +#endif if (initial_loading) { _repository_changed(0); // Update when shown for the first time. } } } break; + case NOTIFICATION_PROCESS: { HTTPClient::Status s = request->get_http_client_status(); const bool loading = s != HTTPClient::STATUS_DISCONNECTED; @@ -596,16 +623,14 @@ void EditorAssetLibrary::_notification(int p_what) { } } break; - case NOTIFICATION_THEME_CHANGED: { - library_scroll_bg->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - downloads_scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - error_tr->set_texture(get_theme_icon(SNAME("Error"), SNAME("EditorIcons"))); - filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - filter->set_clear_button_enabled(true); + + case NOTIFICATION_RESIZED: { + _update_asset_items_columns(); } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { _update_repository_options(); + setup_http_request(request); } break; } } @@ -623,13 +648,13 @@ void EditorAssetLibrary::_update_repository_options() { } } -void EditorAssetLibrary::unhandled_key_input(const Ref<InputEvent> &p_event) { +void EditorAssetLibrary::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); const Ref<InputEventKey> key = p_event; if (key.is_valid() && key->is_pressed()) { - if (key->get_keycode_with_modifiers() == (KeyModifierMask::CMD | Key::F) && is_visible_in_tree()) { + if (key->is_match(InputEventKey::create_reference(KeyModifierMask::CMD_OR_CTRL | Key::F)) && is_visible_in_tree()) { filter->grab_focus(); filter->select_all(); accept_event(); @@ -640,14 +665,10 @@ void EditorAssetLibrary::unhandled_key_input(const Ref<InputEvent> &p_event) { void EditorAssetLibrary::_install_asset() { ERR_FAIL_COND(!description); - for (int i = 0; i < downloads_hb->get_child_count(); i++) { - EditorAssetLibraryItemDownload *d = Object::cast_to<EditorAssetLibraryItemDownload>(downloads_hb->get_child(i)); - if (d && d->get_asset_id() == description->get_asset_id()) { - if (EditorNode::get_singleton() != nullptr) { - EditorNode::get_singleton()->show_warning(TTR("Download for this asset is already in progress!")); - } - return; - } + EditorAssetLibraryItemDownload *d = _get_asset_in_progress(description->get_asset_id()); + if (d) { + d->install(); + return; } EditorAssetLibraryItemDownload *download = memnew(EditorAssetLibraryItemDownload); @@ -684,6 +705,12 @@ const char *EditorAssetLibrary::support_key[SUPPORT_MAX] = { "testing", }; +const char *EditorAssetLibrary::support_text[SUPPORT_MAX] = { + TTRC("Official"), + TTRC("Community"), + TTRC("Testing"), +}; + void EditorAssetLibrary::_select_author(int p_id) { // Open author window. } @@ -714,11 +741,10 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB PackedByteArray image_data = p_data; if (use_cache) { - String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); + String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().path_join("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); - FileAccess *file = FileAccess::open(cache_filename_base + ".data", FileAccess::READ); - - if (file) { + Ref<FileAccess> file = FileAccess::open(cache_filename_base + ".data", FileAccess::READ); + if (file.is_valid()) { PackedByteArray cached_data; int len = file->get_32(); cached_data.resize(len); @@ -727,8 +753,6 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB file->get_buffer(w, len); image_data = cached_data; - file->close(); - memdelete(file); } } @@ -738,13 +762,30 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB uint8_t png_signature[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; uint8_t jpg_signature[3] = { 255, 216, 255 }; + uint8_t webp_signature[4] = { 82, 73, 70, 70 }; + uint8_t bmp_signature[2] = { 66, 77 }; if (r) { if ((memcmp(&r[0], &png_signature[0], 8) == 0) && Image::_png_mem_loader_func) { image->copy_internals_from(Image::_png_mem_loader_func(r, len)); } else if ((memcmp(&r[0], &jpg_signature[0], 3) == 0) && Image::_jpg_mem_loader_func) { image->copy_internals_from(Image::_jpg_mem_loader_func(r, len)); + } else if ((memcmp(&r[0], &webp_signature[0], 4) == 0) && Image::_webp_mem_loader_func) { + image->copy_internals_from(Image::_webp_mem_loader_func(r, len)); + } else if ((memcmp(&r[0], &bmp_signature[0], 2) == 0) && Image::_bmp_mem_loader_func) { + image->copy_internals_from(Image::_bmp_mem_loader_func(r, len)); + } +#ifdef MODULE_SVG_ENABLED + else { + ImageLoaderSVG svg_loader; + Ref<Image> img = Ref<Image>(memnew(Image)); + Error err = svg_loader.create_image_from_utf8_buffer(img, image_data, 1.0, false); + + if (err == OK) { + image->copy_internals_from(img); + } } +#endif } if (!image->is_empty()) { @@ -772,9 +813,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB } break; } - Ref<ImageTexture> tex; - tex.instantiate(); - tex->create_from_image(image); + Ref<ImageTexture> tex = ImageTexture::create_from_image(image); obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, tex); image_set = true; @@ -793,25 +832,19 @@ void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, cons if (p_code != HTTPClient::RESPONSE_NOT_MODIFIED) { for (int i = 0; i < headers.size(); i++) { if (headers[i].findn("ETag:") == 0) { // Save etag - String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); + String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().path_join("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); String new_etag = headers[i].substr(headers[i].find(":") + 1, headers[i].length()).strip_edges(); - FileAccess *file; - - file = FileAccess::open(cache_filename_base + ".etag", FileAccess::WRITE); - if (file) { + Ref<FileAccess> file = FileAccess::open(cache_filename_base + ".etag", FileAccess::WRITE); + if (file.is_valid()) { file->store_line(new_etag); - file->close(); - memdelete(file); } int len = p_data.size(); const uint8_t *r = p_data.ptr(); file = FileAccess::open(cache_filename_base + ".data", FileAccess::WRITE); - if (file) { + if (file.is_valid()) { file->store_32(len); file->store_buffer(r, len); - file->close(); - memdelete(file); } break; @@ -828,7 +861,7 @@ void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, cons } } - image_queue[p_queue_id].request->queue_delete(); + image_queue[p_queue_id].request->queue_free(); image_queue.erase(p_queue_id); _update_image_queue(); @@ -841,15 +874,13 @@ void EditorAssetLibrary::_update_image_queue() { List<int> to_delete; for (KeyValue<int, ImageQueue> &E : image_queue) { if (!E.value.active && current_images < max_images) { - String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + E.value.image_url.md5_text()); + String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().path_join("assetimage_" + E.value.image_url.md5_text()); Vector<String> headers; if (FileAccess::exists(cache_filename_base + ".etag") && FileAccess::exists(cache_filename_base + ".data")) { - FileAccess *file = FileAccess::open(cache_filename_base + ".etag", FileAccess::READ); - if (file) { + Ref<FileAccess> file = FileAccess::open(cache_filename_base + ".etag", FileAccess::READ); + if (file.is_valid()) { headers.push_back("If-None-Match: " + file->get_line()); - file->close(); - memdelete(file); } } @@ -866,7 +897,7 @@ void EditorAssetLibrary::_update_image_queue() { } while (to_delete.size()) { - image_queue[to_delete.front()->get()].request->queue_delete(); + image_queue[to_delete.front()->get()].request->queue_free(); image_queue.erase(to_delete.front()->get()); to_delete.pop_front(); } @@ -884,7 +915,7 @@ void EditorAssetLibrary::_request_image(ObjectID p_for, String p_image_url, Imag iq.queue_id = ++last_queue_id; iq.active = false; - iq.request->connect("request_completed", callable_mp(this, &EditorAssetLibrary::_image_request_completed), varray(iq.queue_id)); + iq.request->connect("request_completed", callable_mp(this, &EditorAssetLibrary::_image_request_completed).bind(iq.queue_id)); image_queue[iq.queue_id] = iq; @@ -895,6 +926,19 @@ void EditorAssetLibrary::_request_image(ObjectID p_for, String p_image_url, Imag } void EditorAssetLibrary::_repository_changed(int p_repository_id) { + library_error->hide(); + library_info->set_text(TTR("Loading...")); + library_info->show(); + + asset_top_page->hide(); + asset_bottom_page->hide(); + asset_items->hide(); + + filter->set_editable(false); + sort->set_disabled(true); + categories->set_disabled(true); + support->set_disabled(true); + host = repository->get_item_metadata(p_repository_id); if (templates_only) { _api_request("configure", REQUESTING_CONFIG, "?type=project"); @@ -963,6 +1007,10 @@ void EditorAssetLibrary::_filter_debounce_timer_timeout() { _search(); } +void EditorAssetLibrary::_request_current_config() { + _repository_changed(repository->get_selected()); +} + HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int p_page_len, int p_total_items, int p_current_items) { HBoxContainer *hbc = memnew(HBoxContainer); @@ -984,9 +1032,9 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int hbc->add_theme_constant_override("separation", 5 * EDSCALE); Button *first = memnew(Button); - first->set_text(TTR("First")); + first->set_text(TTR("First", "Pagination")); if (p_page != 0) { - first->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search), varray(0)); + first->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search).bind(0)); } else { first->set_disabled(true); first->set_focus_mode(Control::FOCUS_NONE); @@ -994,9 +1042,9 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int hbc->add_child(first); Button *prev = memnew(Button); - prev->set_text(TTR("Previous")); + prev->set_text(TTR("Previous", "Pagination")); if (p_page > 0) { - prev->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search), varray(p_page - 1)); + prev->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search).bind(p_page - 1)); } else { prev->set_disabled(true); prev->set_focus_mode(Control::FOCUS_NONE); @@ -1017,16 +1065,16 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int Button *current = memnew(Button); // Add padding to make page number buttons easier to click. current->set_text(vformat(" %d ", i + 1)); - current->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search), varray(i)); + current->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search).bind(i)); hbc->add_child(current); } } Button *next = memnew(Button); - next->set_text(TTR("Next")); + next->set_text(TTR("Next", "Pagination")); if (p_page < p_page_count - 1) { - next->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search), varray(p_page + 1)); + next->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search).bind(p_page + 1)); } else { next->set_disabled(true); next->set_focus_mode(Control::FOCUS_NONE); @@ -1035,9 +1083,9 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int hbc->add_child(next); Button *last = memnew(Button); - last->set_text(TTR("Last")); + last->set_text(TTR("Last", "Pagination")); if (p_page != p_page_count - 1) { - last->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search), varray(p_page_count - 1)); + last->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search).bind(p_page_count - 1)); } else { last->set_disabled(true); last->set_focus_mode(Control::FOCUS_NONE); @@ -1080,7 +1128,7 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const case HTTPRequest::RESULT_CHUNKED_BODY_SIZE_MISMATCH: { error_label->set_text(TTR("Connection error, please try again.")); } break; - case HTTPRequest::RESULT_SSL_HANDSHAKE_ERROR: + case HTTPRequest::RESULT_TLS_HANDSHAKE_ERROR: case HTTPRequest::RESULT_CANT_CONNECT: { error_label->set_text(TTR("Can't connect to host:") + " " + host); } break; @@ -1104,6 +1152,10 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const } if (error_abort) { + if (requesting == REQUESTING_CONFIG) { + library_info->hide(); + library_error->show(); + } error_hb->show(); return; } @@ -1133,22 +1185,21 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const String name = cat["name"]; int id = cat["id"]; categories->add_item(name); - categories->set_item_metadata(categories->get_item_count() - 1, id); + categories->set_item_metadata(-1, id); category_map[cat["id"]] = name; } } + filter->set_editable(true); + sort->set_disabled(false); + categories->set_disabled(false); + support->set_disabled(false); + _search(); } break; case REQUESTING_SEARCH: { initial_loading = false; - // The loading text only needs to be displayed before the first page is loaded. - // Therefore, we don't need to show it again. - library_loading->hide(); - - library_error->hide(); - if (asset_items) { memdelete(asset_items); } @@ -1187,9 +1238,9 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const library_vb->add_child(asset_top_page); asset_items = memnew(GridContainer); - asset_items->set_columns(2); - asset_items->add_theme_constant_override("hseparation", 10 * EDSCALE); - asset_items->add_theme_constant_override("vseparation", 10 * EDSCALE); + _update_asset_items_columns(); + asset_items->add_theme_constant_override("h_separation", 10 * EDSCALE); + asset_items->add_theme_constant_override("v_separation", 10 * EDSCALE); library_vb->add_child(asset_items); @@ -1197,17 +1248,32 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const library_vb->add_child(asset_bottom_page); if (result.is_empty()) { + String support_list; + for (int i = 0; i < SUPPORT_MAX; i++) { + if (support->get_popup()->is_item_checked(i)) { + if (!support_list.is_empty()) { + support_list += ", "; + } + support_list += TTRGET(support_text[i]); + } + } + if (support_list.is_empty()) { + support_list = "-"; + } + if (!filter->get_text().is_empty()) { - library_error->set_text( - vformat(TTR("No results for \"%s\"."), filter->get_text())); + library_info->set_text( + vformat(TTR("No results for \"%s\" for support level(s): %s."), filter->get_text(), support_list)); } else { // No results, even though the user didn't search for anything specific. // This is typically because the version number changed recently // and no assets compatible with the new version have been published yet. - library_error->set_text( - vformat(TTR("No results compatible with %s %s."), String(VERSION_SHORT_NAME).capitalize(), String(VERSION_BRANCH))); + library_info->set_text( + vformat(TTR("No results compatible with %s %s for support level(s): %s.\nCheck the enabled support levels using the 'Support' button in the top-right corner."), String(VERSION_SHORT_NAME).capitalize(), String(VERSION_BRANCH), support_list)); } - library_error->show(); + library_info->show(); + } else { + library_info->hide(); } for (int i = 0; i < result.size(); i++) { @@ -1265,6 +1331,20 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const description->configure(r["title"], r["asset_id"], category_map[r["category_id"]], r["category_id"], r["author"], r["author_id"], r["cost"], r["version"], r["version_string"], r["description"], r["download_url"], r["browse_url"], r["download_hash"]); + EditorAssetLibraryItemDownload *download_item = _get_asset_in_progress(description->get_asset_id()); + if (download_item) { + if (download_item->can_install()) { + description->set_ok_button_text(TTR("Install")); + description->get_ok_button()->set_disabled(false); + } else { + description->set_ok_button_text(TTR("Downloading...")); + description->get_ok_button()->set_disabled(true); + } + } else { + description->set_ok_button_text(TTR("Download")); + description->get_ok_button()->set_disabled(false); + } + if (r.has("icon_url") && !r["icon_url"].operator String().is_empty()) { _request_image(description->get_instance_id(), r["icon_url"], IMAGE_QUEUE_ICON, 0); } @@ -1322,10 +1402,30 @@ void EditorAssetLibrary::_manage_plugins() { ProjectSettingsEditor::get_singleton()->set_plugins_page(); } +EditorAssetLibraryItemDownload *EditorAssetLibrary::_get_asset_in_progress(int p_asset_id) const { + for (int i = 0; i < downloads_hb->get_child_count(); i++) { + EditorAssetLibraryItemDownload *d = Object::cast_to<EditorAssetLibraryItemDownload>(downloads_hb->get_child(i)); + if (d && d->get_asset_id() == p_asset_id) { + return d; + } + } + + return nullptr; +} + void EditorAssetLibrary::_install_external_asset(String p_zip_path, String p_title) { emit_signal(SNAME("install_asset"), p_zip_path, p_title); } +void EditorAssetLibrary::_update_asset_items_columns() { + int new_columns = get_size().x / (450.0 * EDSCALE); + new_columns = MAX(1, new_columns); + + if (new_columns != asset_items->get_columns()) { + asset_items->set_columns(new_columns); + } +} + void EditorAssetLibrary::disable_community_support() { support->get_popup()->set_item_checked(SUPPORT_COMMUNITY, false); } @@ -1340,7 +1440,6 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { initial_loading = true; VBoxContainer *library_main = memnew(VBoxContainer); - add_child(library_main); HBoxContainer *search_hb = memnew(HBoxContainer); @@ -1350,10 +1449,11 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { filter = memnew(LineEdit); if (templates_only) { - filter->set_placeholder(TTR("Search templates, projects, and demos")); + filter->set_placeholder(TTR("Search Templates, Projects, and Demos")); } else { - filter->set_placeholder(TTR("Search assets (excluding templates, projects, and demos)")); + filter->set_placeholder(TTR("Search Assets (Excluding Templates, Projects, and Demos)")); } + filter->set_clear_button_enabled(true); search_hb->add_child(filter); filter->set_h_size_flags(Control::SIZE_EXPAND_FILL); filter->connect("text_changed", callable_mp(this, &EditorAssetLibrary::_search_text_changed)); @@ -1397,6 +1497,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { search_hb2->add_child(sort); sort->set_h_size_flags(Control::SIZE_EXPAND_FILL); + sort->set_clip_text(true); sort->connect("item_selected", callable_mp(this, &EditorAssetLibrary::_rerun_search)); search_hb2->add_child(memnew(VSeparator)); @@ -1406,6 +1507,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { categories->add_item(TTR("All")); search_hb2->add_child(categories); categories->set_h_size_flags(Control::SIZE_EXPAND_FILL); + categories->set_clip_text(true); categories->connect("item_selected", callable_mp(this, &EditorAssetLibrary::_rerun_search)); search_hb2->add_child(memnew(VSeparator)); @@ -1419,6 +1521,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { search_hb2->add_child(repository); repository->set_h_size_flags(Control::SIZE_EXPAND_FILL); + repository->set_clip_text(true); search_hb2->add_child(memnew(VSeparator)); @@ -1426,9 +1529,9 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { search_hb2->add_child(support); support->set_text(TTR("Support")); support->get_popup()->set_hide_on_checkable_item_selection(false); - support->get_popup()->add_check_item(TTR("Official"), SUPPORT_OFFICIAL); - support->get_popup()->add_check_item(TTR("Community"), SUPPORT_COMMUNITY); - support->get_popup()->add_check_item(TTR("Testing"), SUPPORT_TESTING); + support->get_popup()->add_check_item(TTRGET(support_text[SUPPORT_OFFICIAL]), SUPPORT_OFFICIAL); + support->get_popup()->add_check_item(TTRGET(support_text[SUPPORT_COMMUNITY]), SUPPORT_COMMUNITY); + support->get_popup()->add_check_item(TTRGET(support_text[SUPPORT_TESTING]), SUPPORT_TESTING); support->get_popup()->set_item_checked(SUPPORT_OFFICIAL, true); support->get_popup()->set_item_checked(SUPPORT_COMMUNITY, true); support->get_popup()->connect("id_pressed", callable_mp(this, &EditorAssetLibrary::_support_toggled)); @@ -1446,10 +1549,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { Ref<StyleBoxEmpty> border2; border2.instantiate(); - border2->set_default_margin(SIDE_LEFT, 15 * EDSCALE); - border2->set_default_margin(SIDE_RIGHT, 35 * EDSCALE); - border2->set_default_margin(SIDE_BOTTOM, 15 * EDSCALE); - border2->set_default_margin(SIDE_TOP, 15 * EDSCALE); + border2->set_content_margin_individual(15 * EDSCALE, 15 * EDSCALE, 35 * EDSCALE, 15 * EDSCALE); PanelContainer *library_vb_border = memnew(PanelContainer); library_scroll->add_child(library_vb_border); @@ -1461,22 +1561,30 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { library_vb_border->add_child(library_vb); - library_loading = memnew(Label(TTR("Loading..."))); - library_loading->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); - library_vb->add_child(library_loading); + library_info = memnew(Label); + library_info->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + library_vb->add_child(library_info); - library_error = memnew(Label); - library_error->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + library_error = memnew(VBoxContainer); library_error->hide(); library_vb->add_child(library_error); + library_error_label = memnew(Label(TTR("Failed to get repository configuration."))); + library_error_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + library_error->add_child(library_error_label); + + library_error_retry = memnew(Button(TTR("Retry"))); + library_error_retry->set_h_size_flags(SIZE_SHRINK_CENTER); + library_error_retry->connect("pressed", callable_mp(this, &EditorAssetLibrary::_request_current_config)); + library_error->add_child(library_error_retry); + asset_top_page = memnew(HBoxContainer); library_vb->add_child(asset_top_page); asset_items = memnew(GridContainer); - asset_items->set_columns(2); - asset_items->add_theme_constant_override("hseparation", 10 * EDSCALE); - asset_items->add_theme_constant_override("vseparation", 10 * EDSCALE); + _update_asset_items_columns(); + asset_items->add_theme_constant_override("h_separation", 10 * EDSCALE); + asset_items->add_theme_constant_override("v_separation", 10 * EDSCALE); library_vb->add_child(asset_items); @@ -1495,7 +1603,6 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { error_hb = memnew(HBoxContainer); library_main->add_child(error_hb); error_label = memnew(Label); - error_label->add_theme_color_override("color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); error_hb->add_child(error_label); error_tr = memnew(TextureRect); error_tr->set_v_size_flags(Control::SIZE_SHRINK_CENTER); @@ -1504,7 +1611,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { description = nullptr; set_process(true); - set_process_unhandled_key_input(true); // Global shortcuts since there is no main element to be focused. + set_process_shortcut_input(true); // Global shortcuts since there is no main element to be focused. downloads_scroll = memnew(ScrollContainer); downloads_scroll->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); @@ -1515,7 +1622,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { asset_open = memnew(EditorFileDialog); asset_open->set_access(EditorFileDialog::ACCESS_FILESYSTEM); - asset_open->add_filter("*.zip ; " + TTR("Assets ZIP File")); + asset_open->add_filter("*.zip", TTR("Assets ZIP File")); asset_open->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); add_child(asset_open); asset_open->connect("file_selected", callable_mp(this, &EditorAssetLibrary::_asset_file_selected)); @@ -1525,6 +1632,16 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { /////// +bool AssetLibraryEditorPlugin::is_available() { +#ifdef WEB_ENABLED + // Asset Library can't work on Web editor for now as most assets are sourced + // directly from GitHub which does not set CORS. + return false; +#else + return StreamPeerTLS::is_available(); +#endif +} + void AssetLibraryEditorPlugin::make_visible(bool p_visible) { if (p_visible) { addon_library->show(); @@ -1533,12 +1650,11 @@ void AssetLibraryEditorPlugin::make_visible(bool p_visible) { } } -AssetLibraryEditorPlugin::AssetLibraryEditorPlugin(EditorNode *p_node) { - editor = p_node; +AssetLibraryEditorPlugin::AssetLibraryEditorPlugin() { addon_library = memnew(EditorAssetLibrary); addon_library->set_v_size_flags(Control::SIZE_EXPAND_FILL); - editor->get_main_control()->add_child(addon_library); - addon_library->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + EditorNode::get_singleton()->get_main_screen_control()->add_child(addon_library); + addon_library->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); addon_library->hide(); } diff --git a/editor/plugins/asset_library_editor_plugin.h b/editor/plugins/asset_library_editor_plugin.h index d797608c24..8c74da0e2a 100644 --- a/editor/plugins/asset_library_editor_plugin.h +++ b/editor/plugins/asset_library_editor_plugin.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* asset_library_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* asset_library_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 ASSET_LIBRARY_EDITOR_PLUGIN_H #define ASSET_LIBRARY_EDITOR_PLUGIN_H @@ -39,6 +39,7 @@ #include "scene/gui/grid_container.h" #include "scene/gui/line_edit.h" #include "scene/gui/link_button.h" +#include "scene/gui/margin_container.h" #include "scene/gui/option_button.h" #include "scene/gui/panel_container.h" #include "scene/gui/progress_bar.h" @@ -49,19 +50,22 @@ #include "scene/gui/texture_button.h" #include "scene/main/http_request.h" +class EditorFileDialog; +class MenuButton; + class EditorAssetLibraryItem : public PanelContainer { GDCLASS(EditorAssetLibraryItem, PanelContainer); - TextureButton *icon; - LinkButton *title; - LinkButton *category; - LinkButton *author; + TextureButton *icon = nullptr; + LinkButton *title = nullptr; + LinkButton *category = nullptr; + LinkButton *author = nullptr; TextureRect *stars[5]; - Label *price; + Label *price = nullptr; - int asset_id; - int category_id; - int author_id; + int asset_id = 0; + int category_id = 0; + int author_id = 0; void _asset_clicked(); void _category_clicked(); @@ -82,11 +86,11 @@ public: class EditorAssetLibraryItemDescription : public ConfirmationDialog { GDCLASS(EditorAssetLibraryItemDescription, ConfirmationDialog); - EditorAssetLibraryItem *item; - RichTextLabel *description; - ScrollContainer *previews; - HBoxContainer *preview_hb; - PanelContainer *previews_bg; + EditorAssetLibraryItem *item = nullptr; + RichTextLabel *description = nullptr; + ScrollContainer *previews = nullptr; + HBoxContainer *preview_hb = nullptr; + PanelContainer *previews_bg = nullptr; struct Preview { int id = 0; @@ -97,11 +101,11 @@ class EditorAssetLibraryItemDescription : public ConfirmationDialog { }; Vector<Preview> preview_images; - TextureRect *preview; + TextureRect *preview = nullptr; void set_image(int p_type, int p_index, const Ref<Texture2D> &p_image); - int asset_id; + int asset_id = 0; String download_url; String title; String sha256; @@ -126,32 +130,32 @@ public: EditorAssetLibraryItemDescription(); }; -class EditorAssetLibraryItemDownload : public PanelContainer { - GDCLASS(EditorAssetLibraryItemDownload, PanelContainer); +class EditorAssetLibraryItemDownload : public MarginContainer { + GDCLASS(EditorAssetLibraryItemDownload, MarginContainer); - TextureRect *icon; - Label *title; - ProgressBar *progress; - Button *install; - Button *retry; - TextureButton *dismiss; + PanelContainer *panel = nullptr; + TextureRect *icon = nullptr; + Label *title = nullptr; + ProgressBar *progress = nullptr; + Button *install_button = nullptr; + Button *retry_button = nullptr; + TextureButton *dismiss_button = nullptr; - AcceptDialog *download_error; - HTTPRequest *download; + AcceptDialog *download_error = nullptr; + HTTPRequest *download = nullptr; String host; String sha256; - Label *status; + Label *status = nullptr; int prev_status; - int asset_id; + int asset_id = 0; bool external_install; - EditorAssetInstaller *asset_installer; + EditorAssetInstaller *asset_installer = nullptr; void _close(); - void _install(); void _make_request(); void _http_download_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data); @@ -163,6 +167,10 @@ public: void set_external_install(bool p_enable) { external_install = p_enable; } int get_asset_id() { return asset_id; } void configure(const String &p_title, int p_asset_id, const Ref<Texture2D> &p_preview, const String &p_download_url, const String &p_sha256_hash); + + bool can_install() const; + void install(); + EditorAssetLibraryItemDownload(); }; @@ -171,35 +179,37 @@ class EditorAssetLibrary : public PanelContainer { String host; - EditorFileDialog *asset_open; - EditorAssetInstaller *asset_installer; + EditorFileDialog *asset_open = nullptr; + EditorAssetInstaller *asset_installer = nullptr; void _asset_open(); void _asset_file_selected(const String &p_file); void _update_repository_options(); - PanelContainer *library_scroll_bg; - ScrollContainer *library_scroll; - VBoxContainer *library_vb; - Label *library_loading; - Label *library_error; - LineEdit *filter; - Timer *filter_debounce_timer; - OptionButton *categories; - OptionButton *repository; - OptionButton *sort; - HBoxContainer *error_hb; - TextureRect *error_tr; - Label *error_label; - MenuButton *support; - - HBoxContainer *contents; - - HBoxContainer *asset_top_page; - GridContainer *asset_items; - HBoxContainer *asset_bottom_page; - - HTTPRequest *request; + PanelContainer *library_scroll_bg = nullptr; + ScrollContainer *library_scroll = nullptr; + VBoxContainer *library_vb = nullptr; + Label *library_info = nullptr; + VBoxContainer *library_error = nullptr; + Label *library_error_label = nullptr; + Button *library_error_retry = nullptr; + LineEdit *filter = nullptr; + Timer *filter_debounce_timer = nullptr; + OptionButton *categories = nullptr; + OptionButton *repository = nullptr; + OptionButton *sort = nullptr; + HBoxContainer *error_hb = nullptr; + TextureRect *error_tr = nullptr; + Label *error_label = nullptr; + MenuButton *support = nullptr; + + HBoxContainer *contents = nullptr; + + HBoxContainer *asset_top_page = nullptr; + GridContainer *asset_items = nullptr; + HBoxContainer *asset_bottom_page = nullptr; + + HTTPRequest *request = nullptr; bool templates_only; bool initial_loading; @@ -224,6 +234,7 @@ class EditorAssetLibrary : public PanelContainer { static const char *sort_key[SORT_MAX]; static const char *sort_text[SORT_MAX]; static const char *support_key[SUPPORT_MAX]; + static const char *support_text[SUPPORT_MAX]; ///MainListing @@ -245,7 +256,7 @@ class EditorAssetLibrary : public PanelContainer { }; int last_queue_id; - Map<int, ImageQueue> image_queue; + HashMap<int, ImageQueue> image_queue; void _image_update(bool use_cache, bool final, const PackedByteArray &p_data, int p_queue_id); void _image_request_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data, int p_queue_id); @@ -255,7 +266,7 @@ class EditorAssetLibrary : public PanelContainer { HBoxContainer *_make_pages(int p_page, int p_page_count, int p_page_len, int p_total_items, int p_current_items); // - EditorAssetLibraryItemDescription *description; + EditorAssetLibraryItemDescription *description = nullptr; // enum RequestType { @@ -268,8 +279,8 @@ class EditorAssetLibrary : public PanelContainer { RequestType requesting; Dictionary category_map; - ScrollContainer *downloads_scroll; - HBoxContainer *downloads_hb; + ScrollContainer *downloads_scroll = nullptr; + HBoxContainer *downloads_hb = nullptr; void _install_asset(); @@ -286,19 +297,23 @@ class EditorAssetLibrary : public PanelContainer { void _api_request(const String &p_request, RequestType p_request_type, const String &p_arguments = ""); void _http_request_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data); void _filter_debounce_timer_timeout(); + void _request_current_config(); + EditorAssetLibraryItemDownload *_get_asset_in_progress(int p_asset_id) const; void _repository_changed(int p_repository_id); void _support_toggled(int p_support); void _install_external_asset(String p_zip_path, String p_title); + void _update_asset_items_columns(); + friend class EditorAssetLibraryItemDescription; friend class EditorAssetLibraryItem; protected: static void _bind_methods(); void _notification(int p_what); - virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; + virtual void shortcut_input(const Ref<InputEvent> &p_event) override; public: void disable_community_support(); @@ -309,10 +324,11 @@ public: class AssetLibraryEditorPlugin : public EditorPlugin { GDCLASS(AssetLibraryEditorPlugin, EditorPlugin); - EditorAssetLibrary *addon_library; - EditorNode *editor; + EditorAssetLibrary *addon_library = nullptr; public: + static bool is_available(); + virtual String get_name() const override { return "AssetLib"; } bool has_main_screen() const override { return true; } virtual void edit(Object *p_object) override {} @@ -322,8 +338,8 @@ public: //virtual Dictionary get_state() const; //virtual void set_state(const Dictionary& p_state); - AssetLibraryEditorPlugin(EditorNode *p_node); + AssetLibraryEditorPlugin(); ~AssetLibraryEditorPlugin(); }; -#endif // EDITORASSETLIBRARY_H +#endif // ASSET_LIBRARY_EDITOR_PLUGIN_H diff --git a/editor/plugins/audio_stream_editor_plugin.cpp b/editor/plugins/audio_stream_editor_plugin.cpp index 086d5474ba..61f70f6b1e 100644 --- a/editor/plugins/audio_stream_editor_plugin.cpp +++ b/editor/plugins/audio_stream_editor_plugin.cpp @@ -1,66 +1,75 @@ -/*************************************************************************/ -/* audio_stream_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* audio_stream_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 "audio_stream_editor_plugin.h" -#include "core/config/project_settings.h" -#include "core/io/resource_loader.h" -#include "core/os/keyboard.h" +#include "core/core_string_names.h" #include "editor/audio_stream_preview.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "scene/resources/audio_stream_wav.h" -void AudioStreamEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", callable_mp(this, &AudioStreamEditor::_preview_changed)); - } - - if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { - _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); - _stop_button->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); - _preview->set_color(get_theme_color(SNAME("dark_color_2"), SNAME("Editor"))); - set_color(get_theme_color(SNAME("dark_color_1"), SNAME("Editor"))); - - _indicator->update(); - _preview->update(); - } - - if (p_what == NOTIFICATION_PROCESS) { - _current = _player->get_playback_position(); - _indicator->update(); - } +// AudioStreamEditor - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (!is_visible_in_tree()) { - _stop(); - } +void AudioStreamEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: { + AudioStreamPreviewGenerator::get_singleton()->connect(SNAME("preview_updated"), callable_mp(this, &AudioStreamEditor::_preview_changed)); + } break; + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_ENTER_TREE: { + Ref<Font> font = get_theme_font(SNAME("status_source"), SNAME("EditorFonts")); + + _current_label->add_theme_font_override(SNAME("font"), font); + _duration_label->add_theme_font_override(SNAME("font"), font); + + _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + _stop_button->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); + _preview->set_color(get_theme_color(SNAME("dark_color_2"), SNAME("Editor"))); + + set_color(get_theme_color(SNAME("dark_color_1"), SNAME("Editor"))); + + _indicator->queue_redraw(); + _preview->queue_redraw(); + } break; + case NOTIFICATION_PROCESS: { + _current = _player->get_playback_position(); + _indicator->queue_redraw(); + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible_in_tree()) { + _stop(); + } + } break; + default: { + } break; } } @@ -85,33 +94,30 @@ void AudioStreamEditor::_draw_preview() { lines.write[idx * 2 + 1] = Vector2(i + 1, rect.position.y + max * rect.size.y); } - Vector<Color> color; - color.push_back(get_theme_color(SNAME("contrast_color_2"), SNAME("Editor"))); - - RS::get_singleton()->canvas_item_add_multiline(_preview->get_canvas_item(), lines, color); + RS::get_singleton()->canvas_item_add_multiline(_preview->get_canvas_item(), lines, { get_theme_color(SNAME("contrast_color_2"), SNAME("Editor")) }); } void AudioStreamEditor::_preview_changed(ObjectID p_which) { if (stream.is_valid() && stream->get_instance_id() == p_which) { - _preview->update(); + _preview->queue_redraw(); } } -void AudioStreamEditor::_audio_changed() { +void AudioStreamEditor::_stream_changed() { if (!is_visible()) { return; } - update(); + queue_redraw(); } void AudioStreamEditor::_play() { if (_player->is_playing()) { - // '_pausing' variable indicates that we want to pause the audio player, not stop it. See '_on_finished()'. _pausing = true; _player->stop(); _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); set_process(false); } else { + _pausing = false; _player->play(_current); _play_button->set_icon(get_theme_icon(SNAME("Pause"), SNAME("EditorIcons"))); set_process(true); @@ -122,7 +128,7 @@ void AudioStreamEditor::_stop() { _player->stop(); _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); _current = 0; - _indicator->update(); + _indicator->queue_redraw(); set_process(false); } @@ -130,7 +136,7 @@ void AudioStreamEditor::_on_finished() { _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); if (!_pausing) { _current = 0; - _indicator->update(); + _indicator->queue_redraw(); } else { _pausing = false; } @@ -138,19 +144,20 @@ void AudioStreamEditor::_on_finished() { } void AudioStreamEditor::_draw_indicator() { - if (!stream.is_valid()) { + if (stream.is_null()) { return; } Rect2 rect = _preview->get_rect(); float len = stream->get_length(); float ofs_x = _current / len * rect.size.width; - const Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - _indicator->draw_line(Point2(ofs_x, 0), Point2(ofs_x, rect.size.height), color, Math::round(2 * EDSCALE)); + const Color col = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + Ref<Texture2D> icon = get_theme_icon(SNAME("TimelineIndicator"), SNAME("EditorIcons")); + _indicator->draw_line(Point2(ofs_x, 0), Point2(ofs_x, rect.size.height), col, Math::round(2 * EDSCALE)); _indicator->draw_texture( - get_theme_icon(SNAME("TimelineIndicator"), SNAME("EditorIcons")), - Point2(ofs_x - get_theme_icon(SNAME("TimelineIndicator"), SNAME("EditorIcons"))->get_width() * 0.5, 0), - color); + icon, + Point2(ofs_x - icon->get_width() * 0.5, 0), + col); _current_label->set_text(String::num(_current, 2).pad_decimals(2) + " /"); } @@ -176,51 +183,50 @@ void AudioStreamEditor::_seek_to(real_t p_x) { _current = p_x / _preview->get_rect().size.x * stream->get_length(); _current = CLAMP(_current, 0, stream->get_length()); _player->seek(_current); - _indicator->update(); + _indicator->queue_redraw(); } -void AudioStreamEditor::edit(Ref<AudioStream> p_stream) { - if (!stream.is_null()) { - stream->disconnect("changed", callable_mp(this, &AudioStreamEditor::_audio_changed)); +void AudioStreamEditor::set_stream(const Ref<AudioStream> &p_stream) { + if (stream.is_valid()) { + stream->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &AudioStreamEditor::_stream_changed)); } stream = p_stream; + if (stream.is_null()) { + hide(); + return; + } + stream->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &AudioStreamEditor::_stream_changed)); + _player->set_stream(stream); _current = 0; + String text = String::num(stream->get_length(), 2).pad_decimals(2) + "s"; _duration_label->set_text(text); - if (!stream.is_null()) { - stream->connect("changed", callable_mp(this, &AudioStreamEditor::_audio_changed)); - update(); - } else { - hide(); - } -} - -void AudioStreamEditor::_bind_methods() { + queue_redraw(); } AudioStreamEditor::AudioStreamEditor() { set_custom_minimum_size(Size2(1, 100) * EDSCALE); _player = memnew(AudioStreamPlayer); - _player->connect("finished", callable_mp(this, &AudioStreamEditor::_on_finished)); + _player->connect(SNAME("finished"), callable_mp(this, &AudioStreamEditor::_on_finished)); add_child(_player); VBoxContainer *vbox = memnew(VBoxContainer); - vbox->set_anchors_and_offsets_preset(PRESET_WIDE, PRESET_MODE_MINSIZE, 0); + vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); add_child(vbox); _preview = memnew(ColorRect); _preview->set_v_size_flags(SIZE_EXPAND_FILL); - _preview->connect("draw", callable_mp(this, &AudioStreamEditor::_draw_preview)); + _preview->connect(SNAME("draw"), callable_mp(this, &AudioStreamEditor::_draw_preview)); vbox->add_child(_preview); _indicator = memnew(Control); - _indicator->set_anchors_and_offsets_preset(PRESET_WIDE); - _indicator->connect("draw", callable_mp(this, &AudioStreamEditor::_draw_indicator)); - _indicator->connect("gui_input", callable_mp(this, &AudioStreamEditor::_on_input_indicator)); + _indicator->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + _indicator->connect(SNAME("draw"), callable_mp(this, &AudioStreamEditor::_draw_indicator)); + _indicator->connect(SNAME("gui_input"), callable_mp(this, &AudioStreamEditor::_on_input_indicator)); _preview->add_child(_indicator); HBoxContainer *hbox = memnew(HBoxContainer); @@ -228,55 +234,47 @@ AudioStreamEditor::AudioStreamEditor() { vbox->add_child(hbox); _play_button = memnew(Button); - _play_button->set_flat(true); hbox->add_child(_play_button); + _play_button->set_flat(true); _play_button->set_focus_mode(Control::FOCUS_NONE); - _play_button->connect("pressed", callable_mp(this, &AudioStreamEditor::_play)); - _play_button->set_shortcut(ED_SHORTCUT("inspector/audio_preview_play_pause", TTR("Audio Preview Play/Pause"), Key::SPACE)); + _play_button->connect(SNAME("pressed"), callable_mp(this, &AudioStreamEditor::_play)); + _play_button->set_shortcut(ED_SHORTCUT("audio_stream_editor/audio_preview_play_pause", TTR("Audio Preview Play/Pause"), Key::SPACE)); _stop_button = memnew(Button); - _stop_button->set_flat(true); hbox->add_child(_stop_button); + _stop_button->set_flat(true); _stop_button->set_focus_mode(Control::FOCUS_NONE); - _stop_button->connect("pressed", callable_mp(this, &AudioStreamEditor::_stop)); + _stop_button->connect(SNAME("pressed"), callable_mp(this, &AudioStreamEditor::_stop)); _current_label = memnew(Label); _current_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); - _current_label->set_h_size_flags(SIZE_EXPAND_FILL); - _current_label->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); - _current_label->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); + _current_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); _current_label->set_modulate(Color(1, 1, 1, 0.5)); hbox->add_child(_current_label); _duration_label = memnew(Label); - _duration_label->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); - _duration_label->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); hbox->add_child(_duration_label); } -void AudioStreamEditorPlugin::edit(Object *p_object) { - AudioStream *s = Object::cast_to<AudioStream>(p_object); - if (!s) { - return; - } +// EditorInspectorPluginAudioStream - audio_editor->edit(Ref<AudioStream>(s)); +bool EditorInspectorPluginAudioStream::can_handle(Object *p_object) { + return Object::cast_to<AudioStreamWAV>(p_object) != nullptr; } -bool AudioStreamEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("AudioStream"); -} +void EditorInspectorPluginAudioStream::parse_begin(Object *p_object) { + AudioStream *stream = Object::cast_to<AudioStream>(p_object); -void AudioStreamEditorPlugin::make_visible(bool p_visible) { - audio_editor->set_visible(p_visible); -} + editor = memnew(AudioStreamEditor); + editor->set_stream(Ref<AudioStream>(stream)); -AudioStreamEditorPlugin::AudioStreamEditorPlugin(EditorNode *p_node) { - editor = p_node; - audio_editor = memnew(AudioStreamEditor); - add_control_to_container(CONTAINER_PROPERTY_EDITOR_BOTTOM, audio_editor); - audio_editor->hide(); + add_custom_control(editor); } -AudioStreamEditorPlugin::~AudioStreamEditorPlugin() { +// AudioStreamEditorPlugin + +AudioStreamEditorPlugin::AudioStreamEditorPlugin() { + Ref<EditorInspectorPluginAudioStream> plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); } diff --git a/editor/plugins/audio_stream_editor_plugin.h b/editor/plugins/audio_stream_editor_plugin.h index db0e204616..52aa5f6150 100644 --- a/editor/plugins/audio_stream_editor_plugin.h +++ b/editor/plugins/audio_stream_editor_plugin.h @@ -1,46 +1,48 @@ -/*************************************************************************/ -/* audio_stream_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* audio_stream_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 AUDIO_STREAM_EDITOR_PLUGIN_H #define AUDIO_STREAM_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/editor_inspector.h" #include "editor/editor_plugin.h" #include "scene/audio/audio_stream_player.h" +#include "scene/gui/button.h" #include "scene/gui/color_rect.h" -#include "scene/resources/texture.h" +#include "scene/gui/label.h" class AudioStreamEditor : public ColorRect { GDCLASS(AudioStreamEditor, ColorRect); Ref<AudioStream> stream; + AudioStreamPlayer *_player = nullptr; ColorRect *_preview = nullptr; Control *_indicator = nullptr; @@ -54,8 +56,6 @@ class AudioStreamEditor : public ColorRect { bool _dragging = false; bool _pausing = false; - void _audio_changed(); - protected: void _notification(int p_what); void _preview_changed(ObjectID p_which); @@ -66,28 +66,28 @@ protected: void _draw_indicator(); void _on_input_indicator(Ref<InputEvent> p_event); void _seek_to(real_t p_x); - static void _bind_methods(); + void _stream_changed(); public: - void edit(Ref<AudioStream> p_stream); + void set_stream(const Ref<AudioStream> &p_stream); + AudioStreamEditor(); }; +class EditorInspectorPluginAudioStream : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginAudioStream, EditorInspectorPlugin); + AudioStreamEditor *editor = nullptr; + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; +}; + class AudioStreamEditorPlugin : public EditorPlugin { GDCLASS(AudioStreamEditorPlugin, EditorPlugin); - AudioStreamEditor *audio_editor; - EditorNode *editor; - public: - virtual String get_name() const override { return "Audio"; } - bool has_main_screen() const override { return false; } - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - AudioStreamEditorPlugin(EditorNode *p_node); - ~AudioStreamEditorPlugin(); + AudioStreamEditorPlugin(); }; #endif // AUDIO_STREAM_EDITOR_PLUGIN_H diff --git a/editor/plugins/audio_stream_randomizer_editor_plugin.cpp b/editor/plugins/audio_stream_randomizer_editor_plugin.cpp new file mode 100644 index 0000000000..bb3b8a124e --- /dev/null +++ b/editor/plugins/audio_stream_randomizer_editor_plugin.cpp @@ -0,0 +1,122 @@ +/**************************************************************************/ +/* audio_stream_randomizer_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 "audio_stream_randomizer_editor_plugin.h" + +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" + +void AudioStreamRandomizerEditorPlugin::edit(Object *p_object) { +} + +bool AudioStreamRandomizerEditorPlugin::handles(Object *p_object) const { + return false; +} + +void AudioStreamRandomizerEditorPlugin::make_visible(bool p_visible) { +} + +void AudioStreamRandomizerEditorPlugin::_move_stream_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) { + EditorUndoRedoManager *undo_redo_man = Object::cast_to<EditorUndoRedoManager>(p_undo_redo); + ERR_FAIL_NULL(undo_redo_man); + + AudioStreamRandomizer *randomizer = Object::cast_to<AudioStreamRandomizer>(p_edited); + if (!randomizer) { + return; + } + + // Compute the array indices to save. + int begin = 0; + int end; + if (p_array_prefix == "stream_") { + end = randomizer->get_streams_count(); + } else { + ERR_FAIL_MSG("Invalid array prefix for AudioStreamRandomizer."); + } + if (p_from_index < 0) { + // Adding new. + if (p_to_pos >= 0) { + begin = p_to_pos; + } else { + end = 0; // Nothing to save when adding at the end. + } + } else if (p_to_pos < 0) { + // Removing. + begin = p_from_index; + } else { + // Moving. + begin = MIN(p_from_index, p_to_pos); + end = MIN(MAX(p_from_index, p_to_pos) + 1, end); + } + +#define ADD_UNDO(obj, property) undo_redo_man->add_undo_property(obj, property, obj->get(property)); + // Save layers' properties. + if (p_from_index < 0) { + undo_redo_man->add_undo_method(randomizer, "remove_stream", p_to_pos < 0 ? randomizer->get_streams_count() : p_to_pos); + } else if (p_to_pos < 0) { + undo_redo_man->add_undo_method(randomizer, "add_stream", p_from_index, Ref<AudioStream>()); + } + + List<PropertyInfo> properties; + randomizer->get_property_list(&properties); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(p_array_prefix)) { + String str = pi.name.trim_prefix(p_array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + if (array_index >= begin && array_index < end) { + ADD_UNDO(randomizer, pi.name); + } + } + } + } +#undef ADD_UNDO + + if (p_from_index < 0) { + undo_redo_man->add_do_method(randomizer, "add_stream", p_to_pos, Ref<AudioStream>()); + } else if (p_to_pos < 0) { + undo_redo_man->add_do_method(randomizer, "remove_stream", p_from_index); + } else { + undo_redo_man->add_do_method(randomizer, "move_stream", p_from_index, p_to_pos); + } +} + +AudioStreamRandomizerEditorPlugin::AudioStreamRandomizerEditorPlugin() { + EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("AudioStreamRandomizer"), callable_mp(this, &AudioStreamRandomizerEditorPlugin::_move_stream_array_element)); +} + +AudioStreamRandomizerEditorPlugin::~AudioStreamRandomizerEditorPlugin() {} diff --git a/editor/plugins/audio_stream_randomizer_editor_plugin.h b/editor/plugins/audio_stream_randomizer_editor_plugin.h new file mode 100644 index 0000000000..72cdaeee30 --- /dev/null +++ b/editor/plugins/audio_stream_randomizer_editor_plugin.h @@ -0,0 +1,54 @@ +/**************************************************************************/ +/* audio_stream_randomizer_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 AUDIO_STREAM_RANDOMIZER_EDITOR_PLUGIN_H +#define AUDIO_STREAM_RANDOMIZER_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "servers/audio/audio_stream.h" + +class AudioStreamRandomizerEditorPlugin : public EditorPlugin { + GDCLASS(AudioStreamRandomizerEditorPlugin, EditorPlugin); + +private: + void _move_stream_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos); + +public: + virtual String get_name() const override { return "AudioStreamRandomizer"; } + bool has_main_screen() const override { return false; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + AudioStreamRandomizerEditorPlugin(); + ~AudioStreamRandomizerEditorPlugin(); +}; + +#endif // AUDIO_STREAM_RANDOMIZER_EDITOR_PLUGIN_H diff --git a/editor/plugins/bit_map_editor_plugin.cpp b/editor/plugins/bit_map_editor_plugin.cpp new file mode 100644 index 0000000000..30fc60b0e0 --- /dev/null +++ b/editor/plugins/bit_map_editor_plugin.cpp @@ -0,0 +1,84 @@ +/**************************************************************************/ +/* bit_map_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 "bit_map_editor_plugin.h" + +#include "editor/editor_scale.h" +#include "scene/gui/label.h" +#include "scene/gui/texture_rect.h" + +void BitMapEditor::setup(const Ref<BitMap> &p_bitmap) { + texture_rect->set_texture(ImageTexture::create_from_image(p_bitmap->convert_to_image())); + size_label->set_text(vformat(String::utf8("%s×%s"), p_bitmap->get_size().width, p_bitmap->get_size().height)); +} + +BitMapEditor::BitMapEditor() { + texture_rect = memnew(TextureRect); + texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + texture_rect->set_texture_filter(TEXTURE_FILTER_NEAREST); + texture_rect->set_custom_minimum_size(Size2(0, 250) * EDSCALE); + add_child(texture_rect); + + size_label = memnew(Label); + size_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); + add_child(size_label); + + // Reduce extra padding on top and bottom of size label. + Ref<StyleBoxEmpty> stylebox; + stylebox.instantiate(); + stylebox->set_content_margin(SIDE_RIGHT, 4 * EDSCALE); + size_label->add_theme_style_override("normal", stylebox); +} + +/////////////////////// + +bool EditorInspectorPluginBitMap::can_handle(Object *p_object) { + return Object::cast_to<BitMap>(p_object) != nullptr; +} + +void EditorInspectorPluginBitMap::parse_begin(Object *p_object) { + BitMap *bitmap = Object::cast_to<BitMap>(p_object); + if (!bitmap) { + return; + } + Ref<BitMap> bm(bitmap); + + BitMapEditor *editor = memnew(BitMapEditor); + editor->setup(bm); + add_custom_control(editor); +} + +/////////////////////// + +BitMapEditorPlugin::BitMapEditorPlugin() { + Ref<EditorInspectorPluginBitMap> plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} diff --git a/editor/plugins/bit_map_editor_plugin.h b/editor/plugins/bit_map_editor_plugin.h new file mode 100644 index 0000000000..afab1da2f7 --- /dev/null +++ b/editor/plugins/bit_map_editor_plugin.h @@ -0,0 +1,67 @@ +/**************************************************************************/ +/* bit_map_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 BIT_MAP_EDITOR_PLUGIN_H +#define BIT_MAP_EDITOR_PLUGIN_H + +#include "editor/editor_inspector.h" +#include "editor/editor_plugin.h" +#include "scene/resources/bit_map.h" + +class TextureRect; + +class BitMapEditor : public VBoxContainer { + GDCLASS(BitMapEditor, VBoxContainer); + + TextureRect *texture_rect = nullptr; + Label *size_label = nullptr; + +public: + void setup(const Ref<BitMap> &p_bitmap); + + BitMapEditor(); +}; + +class EditorInspectorPluginBitMap : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginBitMap, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; +}; + +class BitMapEditorPlugin : public EditorPlugin { + GDCLASS(BitMapEditorPlugin, EditorPlugin); + +public: + BitMapEditorPlugin(); +}; + +#endif // BIT_MAP_EDITOR_PLUGIN_H diff --git a/editor/plugins/bone_map_editor_plugin.cpp b/editor/plugins/bone_map_editor_plugin.cpp new file mode 100644 index 0000000000..3d94dd13a7 --- /dev/null +++ b/editor/plugins/bone_map_editor_plugin.cpp @@ -0,0 +1,1390 @@ +/**************************************************************************/ +/* bone_map_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 "bone_map_editor_plugin.h" + +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/import/post_import_plugin_skeleton_renamer.h" +#include "editor/import/post_import_plugin_skeleton_rest_fixer.h" +#include "editor/import/post_import_plugin_skeleton_track_organizer.h" +#include "editor/import/scene_import_settings.h" +#include "scene/gui/aspect_ratio_container.h" +#include "scene/gui/separator.h" +#include "scene/gui/texture_rect.h" + +void BoneMapperButton::fetch_textures() { + if (selected) { + set_texture_normal(get_theme_icon(SNAME("BoneMapperHandleSelected"), SNAME("EditorIcons"))); + } else { + set_texture_normal(get_theme_icon(SNAME("BoneMapperHandle"), SNAME("EditorIcons"))); + } + set_offset(SIDE_LEFT, 0); + set_offset(SIDE_RIGHT, 0); + set_offset(SIDE_TOP, 0); + set_offset(SIDE_BOTTOM, 0); + + // Hack to avoid handle color darkening... + set_modulate(EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25)); + + circle = memnew(TextureRect); + circle->set_texture(get_theme_icon(SNAME("BoneMapperHandleCircle"), SNAME("EditorIcons"))); + add_child(circle); + set_state(BONE_MAP_STATE_UNSET); +} + +StringName BoneMapperButton::get_profile_bone_name() const { + return profile_bone_name; +} + +void BoneMapperButton::set_state(BoneMapState p_state) { + switch (p_state) { + case BONE_MAP_STATE_UNSET: { + circle->set_modulate(EDITOR_GET("editors/bone_mapper/handle_colors/unset")); + } break; + case BONE_MAP_STATE_SET: { + circle->set_modulate(EDITOR_GET("editors/bone_mapper/handle_colors/set")); + } break; + case BONE_MAP_STATE_MISSING: { + circle->set_modulate(EDITOR_GET("editors/bone_mapper/handle_colors/missing")); + } break; + case BONE_MAP_STATE_ERROR: { + circle->set_modulate(EDITOR_GET("editors/bone_mapper/handle_colors/error")); + } break; + default: { + } break; + } +} + +bool BoneMapperButton::is_require() const { + return require; +} + +void BoneMapperButton::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + fetch_textures(); + } break; + } +} + +BoneMapperButton::BoneMapperButton(const StringName p_profile_bone_name, bool p_require, bool p_selected) { + profile_bone_name = p_profile_bone_name; + require = p_require; + selected = p_selected; +} + +BoneMapperButton::~BoneMapperButton() { +} + +void BoneMapperItem::create_editor() { + HBoxContainer *hbox = memnew(HBoxContainer); + add_child(hbox); + + skeleton_bone_selector = memnew(EditorPropertyText); + skeleton_bone_selector->set_label(profile_bone_name); + skeleton_bone_selector->set_selectable(false); + skeleton_bone_selector->set_h_size_flags(SIZE_EXPAND_FILL); + skeleton_bone_selector->set_object_and_property(bone_map.ptr(), "bone_map/" + String(profile_bone_name)); + skeleton_bone_selector->update_property(); + skeleton_bone_selector->connect("property_changed", callable_mp(this, &BoneMapperItem::_value_changed)); + hbox->add_child(skeleton_bone_selector); + + picker_button = memnew(Button); + picker_button->set_icon(get_theme_icon(SNAME("ClassList"), SNAME("EditorIcons"))); + picker_button->connect("pressed", callable_mp(this, &BoneMapperItem::_open_picker)); + hbox->add_child(picker_button); + + add_child(memnew(HSeparator)); +} + +void BoneMapperItem::_update_property() { + if (skeleton_bone_selector->get_edited_object() && skeleton_bone_selector->get_edited_property()) { + skeleton_bone_selector->update_property(); + } +} + +void BoneMapperItem::_open_picker() { + emit_signal(SNAME("pick"), profile_bone_name); +} + +void BoneMapperItem::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + bone_map->set(p_property, p_value); +} + +void BoneMapperItem::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editor(); + bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property)); + } break; + case NOTIFICATION_EXIT_TREE: { + if (!bone_map.is_null() && bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property))) { + bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property)); + } + } break; + } +} + +void BoneMapperItem::_bind_methods() { + ADD_SIGNAL(MethodInfo("pick", PropertyInfo(Variant::STRING_NAME, "profile_bone_name"))); +} + +BoneMapperItem::BoneMapperItem(Ref<BoneMap> &p_bone_map, const StringName &p_profile_bone_name) { + bone_map = p_bone_map; + profile_bone_name = p_profile_bone_name; +} + +BoneMapperItem::~BoneMapperItem() { +} + +void BonePicker::create_editors() { + set_title(TTR("Bone Picker:")); + + VBoxContainer *vbox = memnew(VBoxContainer); + add_child(vbox); + + bones = memnew(Tree); + bones->set_select_mode(Tree::SELECT_SINGLE); + bones->set_v_size_flags(Control::SIZE_EXPAND_FILL); + bones->set_hide_root(true); + bones->connect("item_activated", callable_mp(this, &BonePicker::_confirm)); + vbox->add_child(bones); + + create_bones_tree(skeleton); +} + +void BonePicker::create_bones_tree(Skeleton3D *p_skeleton) { + bones->clear(); + + if (!p_skeleton) { + return; + } + + TreeItem *root = bones->create_item(); + + HashMap<int, TreeItem *> items; + + items.insert(-1, root); + + Ref<Texture> bone_icon = get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons")); + + Vector<int> bones_to_process = p_skeleton->get_parentless_bones(); + bool is_first = true; + while (bones_to_process.size() > 0) { + int current_bone_idx = bones_to_process[0]; + bones_to_process.erase(current_bone_idx); + + Vector<int> current_bone_child_bones = p_skeleton->get_bone_children(current_bone_idx); + int child_bone_size = current_bone_child_bones.size(); + for (int i = 0; i < child_bone_size; i++) { + bones_to_process.push_back(current_bone_child_bones[i]); + } + + const int parent_idx = p_skeleton->get_bone_parent(current_bone_idx); + TreeItem *parent_item = items.find(parent_idx)->value; + + TreeItem *joint_item = bones->create_item(parent_item); + items.insert(current_bone_idx, joint_item); + + joint_item->set_text(0, p_skeleton->get_bone_name(current_bone_idx)); + joint_item->set_icon(0, bone_icon); + joint_item->set_selectable(0, true); + joint_item->set_metadata(0, "bones/" + itos(current_bone_idx)); + if (is_first) { + is_first = false; + } else { + joint_item->set_collapsed(true); + } + } +} + +void BonePicker::_confirm() { + _ok_pressed(); +} + +void BonePicker::popup_bones_tree(const Size2i &p_minsize) { + popup_centered(p_minsize); +} + +bool BonePicker::has_selected_bone() { + TreeItem *selected = bones->get_selected(); + if (!selected) { + return false; + } + return true; +} + +StringName BonePicker::get_selected_bone() { + TreeItem *selected = bones->get_selected(); + if (!selected) { + return StringName(); + } + return selected->get_text(0); +} + +void BonePicker::_bind_methods() { +} + +void BonePicker::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + } break; + } +} + +BonePicker::BonePicker(Skeleton3D *p_skeleton) { + skeleton = p_skeleton; +} + +BonePicker::~BonePicker() { +} + +void BoneMapper::create_editor() { + // Create Bone picker. + picker = memnew(BonePicker(skeleton)); + picker->connect("confirmed", callable_mp(this, &BoneMapper::_apply_picker_selection)); + add_child(picker, false, INTERNAL_MODE_FRONT); + + profile_selector = memnew(EditorPropertyResource); + profile_selector->setup(bone_map.ptr(), "profile", "SkeletonProfile"); + profile_selector->set_label("Profile"); + profile_selector->set_selectable(false); + profile_selector->set_object_and_property(bone_map.ptr(), "profile"); + profile_selector->update_property(); + profile_selector->connect("property_changed", callable_mp(this, &BoneMapper::_profile_changed)); + add_child(profile_selector); + add_child(memnew(HSeparator)); + + HBoxContainer *group_hbox = memnew(HBoxContainer); + add_child(group_hbox); + + profile_group_selector = memnew(EditorPropertyEnum); + profile_group_selector->set_label("Group"); + profile_group_selector->set_selectable(false); + profile_group_selector->set_h_size_flags(SIZE_EXPAND_FILL); + profile_group_selector->set_object_and_property(this, "current_group_idx"); + profile_group_selector->update_property(); + profile_group_selector->connect("property_changed", callable_mp(this, &BoneMapper::_value_changed)); + group_hbox->add_child(profile_group_selector); + + clear_mapping_button = memnew(Button); + clear_mapping_button->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons"))); + clear_mapping_button->set_tooltip_text(TTR("Clear mappings in current group.")); + clear_mapping_button->connect("pressed", callable_mp(this, &BoneMapper::_clear_mapping_current_group)); + group_hbox->add_child(clear_mapping_button); + + bone_mapper_field = memnew(AspectRatioContainer); + bone_mapper_field->set_stretch_mode(AspectRatioContainer::STRETCH_FIT); + bone_mapper_field->set_custom_minimum_size(Vector2(0, 256.0) * EDSCALE); + bone_mapper_field->set_h_size_flags(Control::SIZE_FILL); + add_child(bone_mapper_field); + + profile_bg = memnew(ColorRect); + profile_bg->set_color(Color(0, 0, 0, 1)); + profile_bg->set_h_size_flags(Control::SIZE_FILL); + profile_bg->set_v_size_flags(Control::SIZE_FILL); + bone_mapper_field->add_child(profile_bg); + + profile_texture = memnew(TextureRect); + profile_texture->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + profile_texture->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); + profile_texture->set_h_size_flags(Control::SIZE_FILL); + profile_texture->set_v_size_flags(Control::SIZE_FILL); + bone_mapper_field->add_child(profile_texture); + + mapper_item_vbox = memnew(VBoxContainer); + add_child(mapper_item_vbox); + + recreate_items(); +} + +void BoneMapper::update_group_idx() { + if (!bone_map->get_profile().is_valid()) { + return; + } + + PackedStringArray group_names; + int len = bone_map->get_profile()->get_group_size(); + for (int i = 0; i < len; i++) { + group_names.push_back(bone_map->get_profile()->get_group_name(i)); + } + if (current_group_idx >= len) { + current_group_idx = 0; + } + if (len > 0) { + profile_group_selector->setup(group_names); + profile_group_selector->update_property(); + profile_group_selector->set_read_only(false); + } +} + +void BoneMapper::_pick_bone(const StringName &p_bone_name) { + picker_key_name = p_bone_name; + picker->popup_bones_tree(Size2(500, 500) * EDSCALE); +} + +void BoneMapper::_apply_picker_selection() { + if (!picker->has_selected_bone()) { + return; + } + bone_map->set_skeleton_bone_name(picker_key_name, picker->get_selected_bone()); +} + +void BoneMapper::set_current_group_idx(int p_group_idx) { + current_group_idx = p_group_idx; + recreate_editor(); +} + +int BoneMapper::get_current_group_idx() const { + return current_group_idx; +} + +void BoneMapper::set_current_bone_idx(int p_bone_idx) { + current_bone_idx = p_bone_idx; + recreate_editor(); +} + +int BoneMapper::get_current_bone_idx() const { + return current_bone_idx; +} + +void BoneMapper::recreate_editor() { + // Clear buttons. + int len = bone_mapper_buttons.size(); + for (int i = 0; i < len; i++) { + profile_texture->remove_child(bone_mapper_buttons[i]); + memdelete(bone_mapper_buttons[i]); + } + bone_mapper_buttons.clear(); + + // Organize mapper items. + len = bone_mapper_items.size(); + for (int i = 0; i < len; i++) { + bone_mapper_items[i]->set_visible(current_bone_idx == i); + } + + Ref<SkeletonProfile> profile = bone_map->get_profile(); + if (profile.is_valid()) { + SkeletonProfileHumanoid *hmn = Object::cast_to<SkeletonProfileHumanoid>(profile.ptr()); + if (hmn) { + StringName hmn_group_name = profile->get_group_name(current_group_idx); + if (hmn_group_name == "Body") { + profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanBody"), SNAME("EditorIcons"))); + } else if (hmn_group_name == "Face") { + profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanFace"), SNAME("EditorIcons"))); + } else if (hmn_group_name == "LeftHand") { + profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanLeftHand"), SNAME("EditorIcons"))); + } else if (hmn_group_name == "RightHand") { + profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanRightHand"), SNAME("EditorIcons"))); + } + } else { + profile_texture->set_texture(profile->get_texture(current_group_idx)); + } + } else { + profile_texture->set_texture(Ref<Texture2D>()); + } + + if (!profile.is_valid()) { + return; + } + + for (int i = 0; i < len; i++) { + if (profile->get_group(i) == profile->get_group_name(current_group_idx)) { + BoneMapperButton *mb = memnew(BoneMapperButton(profile->get_bone_name(i), profile->is_require(i), current_bone_idx == i)); + mb->connect("pressed", callable_mp(this, &BoneMapper::set_current_bone_idx).bind(i), CONNECT_DEFERRED); + mb->set_h_grow_direction(GROW_DIRECTION_BOTH); + mb->set_v_grow_direction(GROW_DIRECTION_BOTH); + Vector2 vc = profile->get_handle_offset(i); + bone_mapper_buttons.push_back(mb); + profile_texture->add_child(mb); + mb->set_anchor(SIDE_LEFT, vc.x); + mb->set_anchor(SIDE_RIGHT, vc.x); + mb->set_anchor(SIDE_TOP, vc.y); + mb->set_anchor(SIDE_BOTTOM, vc.y); + } + } + + _update_state(); +} + +void BoneMapper::clear_items() { + // Clear items. + int len = bone_mapper_items.size(); + for (int i = 0; i < len; i++) { + bone_mapper_items[i]->disconnect("pick", callable_mp(this, &BoneMapper::_pick_bone)); + mapper_item_vbox->remove_child(bone_mapper_items[i]); + memdelete(bone_mapper_items[i]); + } + bone_mapper_items.clear(); +} + +void BoneMapper::recreate_items() { + clear_items(); + // Create items by profile. + Ref<SkeletonProfile> profile = bone_map->get_profile(); + if (profile.is_valid()) { + int len = profile->get_bone_size(); + for (int i = 0; i < len; i++) { + StringName bn = profile->get_bone_name(i); + bone_mapper_items.append(memnew(BoneMapperItem(bone_map, bn))); + bone_mapper_items[i]->connect("pick", callable_mp(this, &BoneMapper::_pick_bone), CONNECT_DEFERRED); + mapper_item_vbox->add_child(bone_mapper_items[i]); + } + } + + update_group_idx(); + recreate_editor(); +} + +void BoneMapper::_update_state() { + int len = bone_mapper_buttons.size(); + for (int i = 0; i < len; i++) { + StringName pbn = bone_mapper_buttons[i]->get_profile_bone_name(); + StringName sbn = bone_map->get_skeleton_bone_name(pbn); + int bone_idx = skeleton->find_bone(sbn); + if (bone_idx >= 0) { + if (bone_map->get_skeleton_bone_name_count(sbn) == 1) { + Ref<SkeletonProfile> prof = bone_map->get_profile(); + + StringName parent_name = prof->get_bone_parent(prof->find_bone(pbn)); + Vector<int> prof_parent_bones; + while (parent_name != StringName()) { + prof_parent_bones.push_back(skeleton->find_bone(bone_map->get_skeleton_bone_name(parent_name))); + if (prof->find_bone(parent_name) == -1) { + break; + } + parent_name = prof->get_bone_parent(prof->find_bone(parent_name)); + } + + int parent_id = skeleton->get_bone_parent(bone_idx); + Vector<int> skel_parent_bones; + while (parent_id >= 0) { + skel_parent_bones.push_back(parent_id); + parent_id = skeleton->get_bone_parent(parent_id); + } + + bool is_broken = false; + for (int j = 0; j < prof_parent_bones.size(); j++) { + if (prof_parent_bones[j] != -1 && !skel_parent_bones.has(prof_parent_bones[j])) { + is_broken = true; + } + } + + if (is_broken) { + bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR); + } else { + bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_SET); + } + } else { + bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR); + } + } else { + if (bone_mapper_buttons[i]->is_require()) { + bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_MISSING); + } else { + bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_UNSET); + } + } + } +} + +void BoneMapper::_clear_mapping_current_group() { + if (bone_map.is_valid()) { + Ref<SkeletonProfile> profile = bone_map->get_profile(); + if (profile.is_valid() && profile->get_group_size() > 0) { + int len = profile->get_bone_size(); + for (int i = 0; i < len; i++) { + if (profile->get_group(i) == profile->get_group_name(current_group_idx)) { + bone_map->_set_skeleton_bone_name(profile->get_bone_name(i), StringName()); + } + } + recreate_items(); + } + } +} + +#ifdef MODULE_REGEX_ENABLED +int BoneMapper::search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_picklist, BoneSegregation p_segregation, int p_parent, int p_child, int p_children_count) { + // There may be multiple candidates hit by existing the subsidiary bone. + // The one with the shortest name is probably the original. + LocalVector<String> hit_list; + String shortest = ""; + + for (int word_idx = 0; word_idx < p_picklist.size(); word_idx++) { + RegEx re = RegEx(p_picklist[word_idx]); + if (p_child == -1) { + Vector<int> bones_to_process = p_parent == -1 ? p_skeleton->get_parentless_bones() : p_skeleton->get_bone_children(p_parent); + while (bones_to_process.size() > 0) { + int idx = bones_to_process[0]; + bones_to_process.erase(idx); + Vector<int> children = p_skeleton->get_bone_children(idx); + for (int i = 0; i < children.size(); i++) { + bones_to_process.push_back(children[i]); + } + + if (p_children_count == 0 && children.size() > 0) { + continue; + } + if (p_children_count > 0 && children.size() < p_children_count) { + continue; + } + + String bn = skeleton->get_bone_name(idx); + if (!re.search(bn.to_lower()).is_null() && guess_bone_segregation(bn) == p_segregation) { + hit_list.push_back(bn); + } + } + + if (hit_list.size() > 0) { + shortest = hit_list[0]; + for (const String &hit : hit_list) { + if (hit.length() < shortest.length()) { + shortest = hit; // Prioritize parent. + } + } + } + } else { + int idx = skeleton->get_bone_parent(p_child); + while (idx != p_parent && idx >= 0) { + Vector<int> children = p_skeleton->get_bone_children(idx); + if (p_children_count == 0 && children.size() > 0) { + continue; + } + if (p_children_count > 0 && children.size() < p_children_count) { + continue; + } + + String bn = skeleton->get_bone_name(idx); + if (!re.search(bn.to_lower()).is_null() && guess_bone_segregation(bn) == p_segregation) { + hit_list.push_back(bn); + } + idx = skeleton->get_bone_parent(idx); + } + + if (hit_list.size() > 0) { + shortest = hit_list[0]; + for (const String &hit : hit_list) { + if (hit.length() <= shortest.length()) { + shortest = hit; // Prioritize parent. + } + } + } + } + + if (shortest != "") { + break; + } + } + + if (shortest == "") { + return -1; + } + + return skeleton->find_bone(shortest); +} + +BoneMapper::BoneSegregation BoneMapper::guess_bone_segregation(String p_bone_name) { + String fixed_bn = p_bone_name.to_snake_case(); + + LocalVector<String> left_words; + left_words.push_back("(?<![a-zA-Z])left"); + left_words.push_back("(?<![a-zA-Z0-9])l(?![a-zA-Z0-9])"); + + LocalVector<String> right_words; + right_words.push_back("(?<![a-zA-Z])right"); + right_words.push_back("(?<![a-zA-Z0-9])r(?![a-zA-Z0-9])"); + + for (uint32_t i = 0; i < left_words.size(); i++) { + RegEx re_l = RegEx(left_words[i]); + if (!re_l.search(fixed_bn).is_null()) { + return BONE_SEGREGATION_LEFT; + } + RegEx re_r = RegEx(right_words[i]); + if (!re_r.search(fixed_bn).is_null()) { + return BONE_SEGREGATION_RIGHT; + } + } + + return BONE_SEGREGATION_NONE; +} + +void BoneMapper::_run_auto_mapping() { + auto_mapping_process(bone_map); + recreate_items(); +} + +void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) { + WARN_PRINT("Run auto mapping."); + + int bone_idx = -1; + Vector<String> picklist; // Use Vector<String> because match words have priority. + Vector<int> search_path; + + // 1. Guess Hips + picklist.push_back("hip"); + picklist.push_back("pelvis"); + picklist.push_back("waist"); + picklist.push_back("torso"); + int hips = search_bone_by_name(skeleton, picklist); + if (hips == -1) { + WARN_PRINT("Auto Mapping couldn't guess Hips. Abort auto mapping."); + return; // If there is no Hips, we cannot guess bone after then. + } else { + p_bone_map->_set_skeleton_bone_name("Hips", skeleton->get_bone_name(hips)); + } + picklist.clear(); + + // 2. Guess Root + bone_idx = skeleton->get_bone_parent(hips); + while (bone_idx >= 0) { + search_path.push_back(bone_idx); + bone_idx = skeleton->get_bone_parent(bone_idx); + } + if (search_path.size() == 0) { + bone_idx = -1; + } else if (search_path.size() == 1) { + bone_idx = search_path[0]; // It is only one bone which can be root. + } else { + bool found = false; + for (int i = 0; i < search_path.size(); i++) { + RegEx re = RegEx("root"); + if (!re.search(skeleton->get_bone_name(search_path[i]).to_lower()).is_null()) { + bone_idx = search_path[i]; // Name match is preferred. + found = true; + break; + } + } + if (!found) { + for (int i = 0; i < search_path.size(); i++) { + if (skeleton->get_bone_global_rest(search_path[i]).origin.is_zero_approx()) { + bone_idx = search_path[i]; // The bone existing at the origin is appropriate as a root. + found = true; + break; + } + } + } + if (!found) { + bone_idx = search_path[search_path.size() - 1]; // Ambiguous, but most parental bone selected. + } + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess Root."); // Root is not required, so continue. + } else { + p_bone_map->_set_skeleton_bone_name("Root", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + search_path.clear(); + + // 3. Guess Neck + picklist.push_back("neck"); + picklist.push_back("head"); // For no neck model. + picklist.push_back("face"); // Same above. + int neck = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, hips); + picklist.clear(); + + // 4. Guess Head + picklist.push_back("head"); + picklist.push_back("face"); + int head = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck); + if (head == -1) { + search_path = skeleton->get_bone_children(neck); + if (search_path.size() == 1) { + head = search_path[0]; // Maybe only one child of the Neck is Head. + } + } + if (head == -1) { + if (neck != -1) { + head = neck; // The head animation should have more movement. + neck = -1; + p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head)); + } else { + WARN_PRINT("Auto Mapping couldn't guess Neck or Head."); // Continued for guessing on the other bones. But abort when guessing spines step. + } + } else { + p_bone_map->_set_skeleton_bone_name("Neck", skeleton->get_bone_name(neck)); + p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head)); + } + picklist.clear(); + search_path.clear(); + + int neck_or_head = neck != -1 ? neck : (head != -1 ? head : -1); + if (neck_or_head != -1) { + // 4-1. Guess Eyes + picklist.push_back("eye(?!.*(brow|lash|lid))"); + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, neck_or_head); + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftEye."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftEye", skeleton->get_bone_name(bone_idx)); + } + + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, neck_or_head); + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightEye."); + } else { + p_bone_map->_set_skeleton_bone_name("RightEye", skeleton->get_bone_name(bone_idx)); + } + picklist.clear(); + + // 4-2. Guess Jaw + picklist.push_back("jaw"); + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck_or_head); + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess Jaw."); + } else { + p_bone_map->_set_skeleton_bone_name("Jaw", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + picklist.clear(); + } + + // 5. Guess Foots + picklist.push_back("foot"); + picklist.push_back("ankle"); + int left_foot = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips); + if (left_foot == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftFoot."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftFoot", skeleton->get_bone_name(left_foot)); + } + int right_foot = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips); + if (right_foot == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightFoot."); + } else { + p_bone_map->_set_skeleton_bone_name("RightFoot", skeleton->get_bone_name(right_foot)); + } + picklist.clear(); + + // 5-1. Guess LowerLegs + picklist.push_back("(low|under).*leg"); + picklist.push_back("knee"); + picklist.push_back("shin"); + picklist.push_back("calf"); + picklist.push_back("leg"); + int left_lower_leg = -1; + if (left_foot != -1) { + left_lower_leg = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_foot); + } + if (left_lower_leg == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftLowerLeg."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftLowerLeg", skeleton->get_bone_name(left_lower_leg)); + } + int right_lower_leg = -1; + if (right_foot != -1) { + right_lower_leg = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_foot); + } + if (right_lower_leg == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightLowerLeg."); + } else { + p_bone_map->_set_skeleton_bone_name("RightLowerLeg", skeleton->get_bone_name(right_lower_leg)); + } + picklist.clear(); + + // 5-2. Guess UpperLegs + picklist.push_back("up.*leg"); + picklist.push_back("thigh"); + picklist.push_back("leg"); + if (left_lower_leg != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_lower_leg); + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftUpperLeg."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftUpperLeg", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + if (right_lower_leg != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_lower_leg); + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightUpperLeg."); + } else { + p_bone_map->_set_skeleton_bone_name("RightUpperLeg", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + picklist.clear(); + + // 5-3. Guess Toes + picklist.push_back("toe"); + picklist.push_back("ball"); + if (left_foot != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_foot); + if (bone_idx == -1) { + search_path = skeleton->get_bone_children(left_foot); + if (search_path.size() == 1) { + bone_idx = search_path[0]; // Maybe only one child of the Foot is Toes. + } + search_path.clear(); + } + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftToes."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftToes", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + if (right_foot != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_foot); + if (bone_idx == -1) { + search_path = skeleton->get_bone_children(right_foot); + if (search_path.size() == 1) { + bone_idx = search_path[0]; // Maybe only one child of the Foot is Toes. + } + search_path.clear(); + } + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightToes."); + } else { + p_bone_map->_set_skeleton_bone_name("RightToes", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + picklist.clear(); + + // 6. Guess Hands + picklist.push_back("hand"); + picklist.push_back("wrist"); + picklist.push_back("palm"); + picklist.push_back("fingers"); + int left_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, -1, 5); + if (left_hand_or_palm == -1) { + // Ambiguous, but try again for fewer finger models. + left_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips); + } + int left_hand = left_hand_or_palm; // Check for the presence of a wrist, since bones with five children may be palmar. + while (left_hand != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_hand); + if (bone_idx == -1) { + break; + } + left_hand = bone_idx; + } + if (left_hand == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftHand."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftHand", skeleton->get_bone_name(left_hand)); + } + bone_idx = -1; + int right_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, -1, 5); + if (right_hand_or_palm == -1) { + // Ambiguous, but try again for fewer finger models. + right_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips); + } + int right_hand = right_hand_or_palm; + while (right_hand != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_hand); + if (bone_idx == -1) { + break; + } + right_hand = bone_idx; + } + if (right_hand == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightHand."); + } else { + p_bone_map->_set_skeleton_bone_name("RightHand", skeleton->get_bone_name(right_hand)); + } + bone_idx = -1; + picklist.clear(); + + // 6-1. Guess Finger + bool named_finger_is_found = false; + LocalVector<String> fingers; + fingers.push_back("thumb|pollex"); + fingers.push_back("index|fore"); + fingers.push_back("middle"); + fingers.push_back("ring"); + fingers.push_back("little|pinkie|pinky"); + if (left_hand_or_palm != -1) { + LocalVector<LocalVector<String>> left_fingers_map; + left_fingers_map.resize(5); + left_fingers_map[0].push_back("LeftThumbMetacarpal"); + left_fingers_map[0].push_back("LeftThumbProximal"); + left_fingers_map[0].push_back("LeftThumbDistal"); + left_fingers_map[1].push_back("LeftIndexProximal"); + left_fingers_map[1].push_back("LeftIndexIntermediate"); + left_fingers_map[1].push_back("LeftIndexDistal"); + left_fingers_map[2].push_back("LeftMiddleProximal"); + left_fingers_map[2].push_back("LeftMiddleIntermediate"); + left_fingers_map[2].push_back("LeftMiddleDistal"); + left_fingers_map[3].push_back("LeftRingProximal"); + left_fingers_map[3].push_back("LeftRingIntermediate"); + left_fingers_map[3].push_back("LeftRingDistal"); + left_fingers_map[4].push_back("LeftLittleProximal"); + left_fingers_map[4].push_back("LeftLittleIntermediate"); + left_fingers_map[4].push_back("LeftLittleDistal"); + for (int i = 0; i < 5; i++) { + picklist.push_back(fingers[i]); + int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_hand_or_palm, -1, 0); + if (finger != -1) { + while (finger != left_hand_or_palm && finger >= 0) { + search_path.push_back(finger); + finger = skeleton->get_bone_parent(finger); + } + search_path.reverse(); + if (search_path.size() == 1) { + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + named_finger_is_found = true; + } else if (search_path.size() == 2) { + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + named_finger_is_found = true; + } else if (search_path.size() >= 3) { + search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone. + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][2], skeleton->get_bone_name(search_path[2])); + named_finger_is_found = true; + } + } + picklist.clear(); + search_path.clear(); + } + + // It is a bit corner case, but possibly the finger names are sequentially numbered... + if (!named_finger_is_found) { + picklist.push_back("finger"); + RegEx finger_re = RegEx("finger"); + search_path = skeleton->get_bone_children(left_hand_or_palm); + Vector<String> finger_names; + for (int i = 0; i < search_path.size(); i++) { + String bn = skeleton->get_bone_name(search_path[i]); + if (!finger_re.search(bn.to_lower()).is_null()) { + finger_names.push_back(bn); + } + } + finger_names.sort(); // Order by lexicographic, normal use cases never have more than 10 fingers in one hand. + search_path.clear(); + for (int i = 0; i < finger_names.size(); i++) { + if (i >= 5) { + break; + } + int finger_root = skeleton->find_bone(finger_names[i]); + int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, finger_root, -1, 0); + if (finger != -1) { + while (finger != finger_root && finger >= 0) { + search_path.push_back(finger); + finger = skeleton->get_bone_parent(finger); + } + } + search_path.push_back(finger_root); + search_path.reverse(); + if (search_path.size() == 1) { + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + } else if (search_path.size() == 2) { + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + } else if (search_path.size() >= 3) { + search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone. + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][2], skeleton->get_bone_name(search_path[2])); + } + search_path.clear(); + } + picklist.clear(); + } + } + named_finger_is_found = false; + if (right_hand_or_palm != -1) { + LocalVector<LocalVector<String>> right_fingers_map; + right_fingers_map.resize(5); + right_fingers_map[0].push_back("RightThumbMetacarpal"); + right_fingers_map[0].push_back("RightThumbProximal"); + right_fingers_map[0].push_back("RightThumbDistal"); + right_fingers_map[1].push_back("RightIndexProximal"); + right_fingers_map[1].push_back("RightIndexIntermediate"); + right_fingers_map[1].push_back("RightIndexDistal"); + right_fingers_map[2].push_back("RightMiddleProximal"); + right_fingers_map[2].push_back("RightMiddleIntermediate"); + right_fingers_map[2].push_back("RightMiddleDistal"); + right_fingers_map[3].push_back("RightRingProximal"); + right_fingers_map[3].push_back("RightRingIntermediate"); + right_fingers_map[3].push_back("RightRingDistal"); + right_fingers_map[4].push_back("RightLittleProximal"); + right_fingers_map[4].push_back("RightLittleIntermediate"); + right_fingers_map[4].push_back("RightLittleDistal"); + for (int i = 0; i < 5; i++) { + picklist.push_back(fingers[i]); + int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_hand_or_palm, -1, 0); + if (finger != -1) { + while (finger != right_hand_or_palm && finger >= 0) { + search_path.push_back(finger); + finger = skeleton->get_bone_parent(finger); + } + search_path.reverse(); + if (search_path.size() == 1) { + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + named_finger_is_found = true; + } else if (search_path.size() == 2) { + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + named_finger_is_found = true; + } else if (search_path.size() >= 3) { + search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone. + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][2], skeleton->get_bone_name(search_path[2])); + named_finger_is_found = true; + } + } + picklist.clear(); + search_path.clear(); + } + + // It is a bit corner case, but possibly the finger names are sequentially numbered... + if (!named_finger_is_found) { + picklist.push_back("finger"); + RegEx finger_re = RegEx("finger"); + search_path = skeleton->get_bone_children(right_hand_or_palm); + Vector<String> finger_names; + for (int i = 0; i < search_path.size(); i++) { + String bn = skeleton->get_bone_name(search_path[i]); + if (!finger_re.search(bn.to_lower()).is_null()) { + finger_names.push_back(bn); + } + } + finger_names.sort(); // Order by lexicographic, normal use cases never have more than 10 fingers in one hand. + search_path.clear(); + for (int i = 0; i < finger_names.size(); i++) { + if (i >= 5) { + break; + } + int finger_root = skeleton->find_bone(finger_names[i]); + int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, finger_root, -1, 0); + if (finger != -1) { + while (finger != finger_root && finger >= 0) { + search_path.push_back(finger); + finger = skeleton->get_bone_parent(finger); + } + } + search_path.push_back(finger_root); + search_path.reverse(); + if (search_path.size() == 1) { + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + } else if (search_path.size() == 2) { + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + } else if (search_path.size() >= 3) { + search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone. + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1])); + p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][2], skeleton->get_bone_name(search_path[2])); + } + search_path.clear(); + } + picklist.clear(); + } + } + + // 7. Guess Arms + picklist.push_back("shoulder"); + picklist.push_back("clavicle"); + picklist.push_back("collar"); + int left_shoulder = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips); + if (left_shoulder == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftShoulder."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftShoulder", skeleton->get_bone_name(left_shoulder)); + } + int right_shoulder = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips); + if (right_shoulder == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightShoulder."); + } else { + p_bone_map->_set_skeleton_bone_name("RightShoulder", skeleton->get_bone_name(right_shoulder)); + } + picklist.clear(); + + // 7-1. Guess LowerArms + picklist.push_back("(low|fore).*arm"); + picklist.push_back("elbow"); + picklist.push_back("arm"); + int left_lower_arm = -1; + if (left_shoulder != -1 && left_hand_or_palm != -1) { + left_lower_arm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_shoulder, left_hand_or_palm); + } + if (left_lower_arm == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftLowerArm."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftLowerArm", skeleton->get_bone_name(left_lower_arm)); + } + int right_lower_arm = -1; + if (right_shoulder != -1 && right_hand_or_palm != -1) { + right_lower_arm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_shoulder, right_hand_or_palm); + } + if (right_lower_arm == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightLowerArm."); + } else { + p_bone_map->_set_skeleton_bone_name("RightLowerArm", skeleton->get_bone_name(right_lower_arm)); + } + picklist.clear(); + + // 7-2. Guess UpperArms + picklist.push_back("up.*arm"); + picklist.push_back("arm"); + if (left_shoulder != -1 && left_lower_arm != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_shoulder, left_lower_arm); + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess LeftUpperArm."); + } else { + p_bone_map->_set_skeleton_bone_name("LeftUpperArm", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + if (right_shoulder != -1 && right_lower_arm != -1) { + bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_shoulder, right_lower_arm); + } + if (bone_idx == -1) { + WARN_PRINT("Auto Mapping couldn't guess RightUpperArm."); + } else { + p_bone_map->_set_skeleton_bone_name("RightUpperArm", skeleton->get_bone_name(bone_idx)); + } + bone_idx = -1; + picklist.clear(); + + // 8. Guess UpperChest or Chest + if (neck_or_head == -1) { + return; // Abort. + } + int chest_or_upper_chest = skeleton->get_bone_parent(neck_or_head); + bool is_appropriate = true; + if (left_shoulder != -1) { + bone_idx = skeleton->get_bone_parent(left_shoulder); + bool detect = false; + while (bone_idx != hips && bone_idx >= 0) { + if (bone_idx == chest_or_upper_chest) { + detect = true; + break; + } + bone_idx = skeleton->get_bone_parent(bone_idx); + } + if (!detect) { + is_appropriate = false; + } + bone_idx = -1; + } + if (right_shoulder != -1) { + bone_idx = skeleton->get_bone_parent(right_shoulder); + bool detect = false; + while (bone_idx != hips && bone_idx >= 0) { + if (bone_idx == chest_or_upper_chest) { + detect = true; + break; + } + bone_idx = skeleton->get_bone_parent(bone_idx); + } + if (!detect) { + is_appropriate = false; + } + bone_idx = -1; + } + if (!is_appropriate) { + if (skeleton->get_bone_parent(left_shoulder) == skeleton->get_bone_parent(right_shoulder)) { + chest_or_upper_chest = skeleton->get_bone_parent(left_shoulder); + } else { + chest_or_upper_chest = -1; + } + } + if (chest_or_upper_chest == -1) { + WARN_PRINT("Auto Mapping couldn't guess Chest or UpperChest. Abort auto mapping."); + return; // Will be not able to guess Spines. + } + + // 9. Guess Spines + bone_idx = skeleton->get_bone_parent(chest_or_upper_chest); + while (bone_idx != hips && bone_idx >= 0) { + search_path.push_back(bone_idx); + bone_idx = skeleton->get_bone_parent(bone_idx); + } + search_path.reverse(); + if (search_path.size() == 0) { + p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(chest_or_upper_chest)); // Maybe chibi model...? + } else if (search_path.size() == 1) { + p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name("Chest", skeleton->get_bone_name(chest_or_upper_chest)); + } else if (search_path.size() >= 2) { + p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(search_path[0])); + p_bone_map->_set_skeleton_bone_name("Chest", skeleton->get_bone_name(search_path[search_path.size() - 1])); // Probably UppeChest's parent is appropriate. + p_bone_map->_set_skeleton_bone_name("UpperChest", skeleton->get_bone_name(chest_or_upper_chest)); + } + bone_idx = -1; + search_path.clear(); + + WARN_PRINT("Finish auto mapping."); +} +#endif // MODULE_REGEX_ENABLED + +void BoneMapper::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + set(p_property, p_value); + recreate_editor(); +} + +void BoneMapper::_profile_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + bone_map->set(p_property, p_value); + + // Run auto mapping when setting SkeletonProfileHumanoid by GUI Editor. + Ref<SkeletonProfile> profile = bone_map->get_profile(); + if (profile.is_valid()) { + SkeletonProfileHumanoid *hmn = Object::cast_to<SkeletonProfileHumanoid>(profile.ptr()); + if (hmn) { +#ifdef MODULE_REGEX_ENABLED + _run_auto_mapping(); +#endif // MODULE_REGEX_ENABLED + } + } +} + +void BoneMapper::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_current_group_idx", "current_group_idx"), &BoneMapper::set_current_group_idx); + ClassDB::bind_method(D_METHOD("get_current_group_idx"), &BoneMapper::get_current_group_idx); + ClassDB::bind_method(D_METHOD("set_current_bone_idx", "current_bone_idx"), &BoneMapper::set_current_bone_idx); + ClassDB::bind_method(D_METHOD("get_current_bone_idx"), &BoneMapper::get_current_bone_idx); + ADD_PROPERTY(PropertyInfo(Variant::INT, "current_group_idx"), "set_current_group_idx", "get_current_group_idx"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "current_bone_idx"), "set_current_bone_idx", "get_current_bone_idx"); +} + +void BoneMapper::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editor(); + bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state)); + bone_map->connect("profile_updated", callable_mp(this, &BoneMapper::recreate_items)); + } break; + case NOTIFICATION_EXIT_TREE: { + clear_items(); + if (!bone_map.is_null()) { + if (bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapper::_update_state))) { + bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state)); + } + if (bone_map->is_connected("profile_updated", callable_mp(this, &BoneMapper::recreate_items))) { + bone_map->disconnect("profile_updated", callable_mp(this, &BoneMapper::recreate_items)); + } + } + } + } +} + +BoneMapper::BoneMapper(Skeleton3D *p_skeleton, Ref<BoneMap> &p_bone_map) { + skeleton = p_skeleton; + bone_map = p_bone_map; +} + +BoneMapper::~BoneMapper() { +} + +void BoneMapEditor::create_editors() { + if (!skeleton) { + return; + } + bone_mapper = memnew(BoneMapper(skeleton, bone_map)); + add_child(bone_mapper); +} + +void BoneMapEditor::fetch_objects() { + skeleton = nullptr; + // Hackey... but it may be the easiest way to get a selected object from "ImporterScene". + SceneImportSettings *si = SceneImportSettings::get_singleton(); + if (!si) { + return; + } + if (!si->is_visible()) { + return; + } + Node *selected = si->get_selected_node(); + if (selected) { + Skeleton3D *sk = Object::cast_to<Skeleton3D>(selected); + if (!sk) { + return; + } + skeleton = sk; + } else { + // Editor should not exist. + skeleton = nullptr; + } +} + +void BoneMapEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + fetch_objects(); + create_editors(); + } break; + case NOTIFICATION_EXIT_TREE: { + skeleton = nullptr; + } break; + } +} + +BoneMapEditor::BoneMapEditor(Ref<BoneMap> &p_bone_map) { + bone_map = p_bone_map; +} + +BoneMapEditor::~BoneMapEditor() { +} + +bool EditorInspectorPluginBoneMap::can_handle(Object *p_object) { + return Object::cast_to<BoneMap>(p_object) != nullptr; +} + +void EditorInspectorPluginBoneMap::parse_begin(Object *p_object) { + BoneMap *bm = Object::cast_to<BoneMap>(p_object); + if (!bm) { + return; + } + Ref<BoneMap> r(bm); + editor = memnew(BoneMapEditor(r)); + add_custom_control(editor); +} + +BoneMapEditorPlugin::BoneMapEditorPlugin() { + // Register properties in editor settings. + EDITOR_DEF("editors/bone_mapper/handle_colors/unset", Color(0.3, 0.3, 0.3)); + EDITOR_DEF("editors/bone_mapper/handle_colors/set", Color(0.1, 0.6, 0.25)); + EDITOR_DEF("editors/bone_mapper/handle_colors/missing", Color(0.8, 0.2, 0.8)); + EDITOR_DEF("editors/bone_mapper/handle_colors/error", Color(0.8, 0.2, 0.2)); + + Ref<EditorInspectorPluginBoneMap> inspector_plugin; + inspector_plugin.instantiate(); + add_inspector_plugin(inspector_plugin); + + Ref<PostImportPluginSkeletonTrackOrganizer> post_import_plugin_track_organizer; + post_import_plugin_track_organizer.instantiate(); + add_scene_post_import_plugin(post_import_plugin_track_organizer); + + Ref<PostImportPluginSkeletonRenamer> post_import_plugin_renamer; + post_import_plugin_renamer.instantiate(); + add_scene_post_import_plugin(post_import_plugin_renamer); + + Ref<PostImportPluginSkeletonRestFixer> post_import_plugin_rest_fixer; + post_import_plugin_rest_fixer.instantiate(); + add_scene_post_import_plugin(post_import_plugin_rest_fixer); +} diff --git a/editor/plugins/bone_map_editor_plugin.h b/editor/plugins/bone_map_editor_plugin.h new file mode 100644 index 0000000000..7974d241a2 --- /dev/null +++ b/editor/plugins/bone_map_editor_plugin.h @@ -0,0 +1,239 @@ +/**************************************************************************/ +/* bone_map_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 BONE_MAP_EDITOR_PLUGIN_H +#define BONE_MAP_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "editor/editor_properties.h" + +#include "modules/modules_enabled.gen.h" // For regex. +#ifdef MODULE_REGEX_ENABLED +#include "modules/regex/regex.h" +#endif + +#include "scene/3d/skeleton_3d.h" +#include "scene/gui/box_container.h" +#include "scene/gui/color_rect.h" +#include "scene/gui/dialogs.h" +#include "scene/resources/bone_map.h" +#include "scene/resources/texture.h" + +class AspectRatioContainer; + +class BoneMapperButton : public TextureButton { + GDCLASS(BoneMapperButton, TextureButton); + +public: + enum BoneMapState { + BONE_MAP_STATE_UNSET, + BONE_MAP_STATE_SET, + BONE_MAP_STATE_MISSING, + BONE_MAP_STATE_ERROR + }; + +private: + StringName profile_bone_name; + bool selected = false; + bool require = false; + + TextureRect *circle = nullptr; + + void fetch_textures(); + +protected: + void _notification(int p_what); + +public: + StringName get_profile_bone_name() const; + void set_state(BoneMapState p_state); + + bool is_require() const; + + BoneMapperButton(const StringName p_profile_bone_name, bool p_require, bool p_selected); + ~BoneMapperButton(); +}; + +class BoneMapperItem : public VBoxContainer { + GDCLASS(BoneMapperItem, VBoxContainer); + + int button_id = -1; + StringName profile_bone_name; + + Ref<BoneMap> bone_map; + + EditorPropertyText *skeleton_bone_selector = nullptr; + Button *picker_button = nullptr; + + void _update_property(); + void _open_picker(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + virtual void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); + virtual void create_editor(); + +public: + void assign_button_id(int p_button_id); + + BoneMapperItem(Ref<BoneMap> &p_bone_map, const StringName &p_profile_bone_name = StringName()); + ~BoneMapperItem(); +}; + +class BonePicker : public AcceptDialog { + GDCLASS(BonePicker, AcceptDialog); + + Skeleton3D *skeleton = nullptr; + Tree *bones = nullptr; + +public: + void popup_bones_tree(const Size2i &p_minsize = Size2i()); + bool has_selected_bone(); + StringName get_selected_bone(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + + void _confirm(); + +private: + void create_editors(); + void create_bones_tree(Skeleton3D *p_skeleton); + +public: + BonePicker(Skeleton3D *p_skeleton); + ~BonePicker(); +}; + +class BoneMapper : public VBoxContainer { + GDCLASS(BoneMapper, VBoxContainer); + + Skeleton3D *skeleton = nullptr; + Ref<BoneMap> bone_map; + + EditorPropertyResource *profile_selector = nullptr; + + Vector<BoneMapperItem *> bone_mapper_items; + + Button *clear_mapping_button = nullptr; + + VBoxContainer *mapper_item_vbox = nullptr; + + int current_group_idx = 0; + int current_bone_idx = -1; + + AspectRatioContainer *bone_mapper_field = nullptr; + EditorPropertyEnum *profile_group_selector = nullptr; + ColorRect *profile_bg = nullptr; + TextureRect *profile_texture = nullptr; + Vector<BoneMapperButton *> bone_mapper_buttons; + + void create_editor(); + void recreate_editor(); + void clear_items(); + void recreate_items(); + void update_group_idx(); + void _update_state(); + + /* Bone picker */ + BonePicker *picker = nullptr; + StringName picker_key_name; + void _pick_bone(const StringName &p_bone_name); + void _apply_picker_selection(); + void _clear_mapping_current_group(); + +#ifdef MODULE_REGEX_ENABLED + /* For auto mapping */ + enum BoneSegregation { + BONE_SEGREGATION_NONE, + BONE_SEGREGATION_LEFT, + BONE_SEGREGATION_RIGHT + }; + int search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_picklist, BoneSegregation p_segregation = BONE_SEGREGATION_NONE, int p_parent = -1, int p_child = -1, int p_children_count = -1); + BoneSegregation guess_bone_segregation(String p_bone_name); + void auto_mapping_process(Ref<BoneMap> &p_bone_map); + void _run_auto_mapping(); +#endif // MODULE_REGEX_ENABLED + +protected: + void _notification(int p_what); + static void _bind_methods(); + virtual void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); + virtual void _profile_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); + +public: + void set_current_group_idx(int p_group_idx); + int get_current_group_idx() const; + void set_current_bone_idx(int p_bone_idx); + int get_current_bone_idx() const; + + BoneMapper(Skeleton3D *p_skeleton, Ref<BoneMap> &p_bone_map); + ~BoneMapper(); +}; + +class BoneMapEditor : public VBoxContainer { + GDCLASS(BoneMapEditor, VBoxContainer); + + Skeleton3D *skeleton = nullptr; + Ref<BoneMap> bone_map; + BoneMapper *bone_mapper = nullptr; + + void fetch_objects(); + void create_editors(); + +protected: + void _notification(int p_what); + +public: + BoneMapEditor(Ref<BoneMap> &p_bone_map); + ~BoneMapEditor(); +}; + +class EditorInspectorPluginBoneMap : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginBoneMap, EditorInspectorPlugin); + BoneMapEditor *editor = nullptr; + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; +}; + +class BoneMapEditorPlugin : public EditorPlugin { + GDCLASS(BoneMapEditorPlugin, EditorPlugin); + +public: + virtual String get_name() const override { return "BoneMap"; } + BoneMapEditorPlugin(); +}; + +#endif // BONE_MAP_EDITOR_PLUGIN_H diff --git a/editor/plugins/camera_3d_editor_plugin.cpp b/editor/plugins/camera_3d_editor_plugin.cpp index 7c920fa15e..b9555296b8 100644 --- a/editor/plugins/camera_3d_editor_plugin.cpp +++ b/editor/plugins/camera_3d_editor_plugin.cpp @@ -1,35 +1,36 @@ -/*************************************************************************/ -/* camera_3d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* camera_3d_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 "camera_3d_editor_plugin.h" +#include "editor/editor_node.h" #include "node_3d_editor_plugin.h" void Camera3DEditor::_node_removed(Node *p_node) { @@ -95,10 +96,9 @@ void Camera3DEditorPlugin::make_visible(bool p_visible) { } } -Camera3DEditorPlugin::Camera3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +Camera3DEditorPlugin::Camera3DEditorPlugin() { /* camera_editor = memnew( CameraEditor ); - editor->get_main_control()->add_child(camera_editor); + EditorNode::get_singleton()->get_main_screen_control()->add_child(camera_editor); camera_editor->set_anchor(SIDE_LEFT,Control::ANCHOR_END); camera_editor->set_anchor(SIDE_RIGHT,Control::ANCHOR_END); diff --git a/editor/plugins/camera_3d_editor_plugin.h b/editor/plugins/camera_3d_editor_plugin.h index e175a931b0..7d5fae6f2b 100644 --- a/editor/plugins/camera_3d_editor_plugin.h +++ b/editor/plugins/camera_3d_editor_plugin.h @@ -1,46 +1,45 @@ -/*************************************************************************/ -/* camera_3d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* camera_3d_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 CAMERA_EDITOR_PLUGIN_H -#define CAMERA_EDITOR_PLUGIN_H +#ifndef CAMERA_3D_EDITOR_PLUGIN_H +#define CAMERA_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/camera_3d.h" class Camera3DEditor : public Control { GDCLASS(Camera3DEditor, Control); - Panel *panel; - Button *preview; - Node *node; + Panel *panel = nullptr; + Button *preview = nullptr; + Node *node = nullptr; void _pressed(); @@ -57,7 +56,6 @@ class Camera3DEditorPlugin : public EditorPlugin { GDCLASS(Camera3DEditorPlugin, EditorPlugin); //CameraEditor *camera_editor; - EditorNode *editor; public: virtual String get_name() const override { return "Camera3D"; } @@ -66,8 +64,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - Camera3DEditorPlugin(EditorNode *p_node); + Camera3DEditorPlugin(); ~Camera3DEditorPlugin(); }; -#endif // CAMERA_EDITOR_PLUGIN_H +#endif // CAMERA_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index d6fd153665..0f9ce89f02 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -1,46 +1,48 @@ -/*************************************************************************/ -/* canvas_item_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* canvas_item_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 "canvas_item_editor_plugin.h" #include "core/config/project_settings.h" #include "core/input/input.h" -#include "core/math/geometry_2d.h" #include "core/os/keyboard.h" -#include "core/string/print_string.h" #include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/editor_toaster.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/editor_zoom_widget.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" +#include "editor/scene_tree_dock.h" #include "scene/2d/cpu_particles_2d.h" #include "scene/2d/gpu_particles_2d.h" #include "scene/2d/light_2d.h" @@ -48,21 +50,25 @@ #include "scene/2d/skeleton_2d.h" #include "scene/2d/sprite_2d.h" #include "scene/2d/touch_screen_button.h" +#include "scene/gui/flow_container.h" #include "scene/gui/grid_container.h" #include "scene/gui/nine_patch_rect.h" +#include "scene/gui/separator.h" +#include "scene/gui/split_container.h" #include "scene/gui/subviewport_container.h" +#include "scene/gui/view_panner.h" #include "scene/main/canvas_layer.h" #include "scene/main/window.h" #include "scene/resources/packed_scene.h" // Min and Max are power of two in order to play nicely with successive increment. // That way, we can naturally reach a 100% zoom from boundaries. -#define MIN_ZOOM 1. / 128 -#define MAX_ZOOM 128 +constexpr real_t MIN_ZOOM = 1. / 128; +constexpr real_t MAX_ZOOM = 128; #define RULER_WIDTH (15 * EDSCALE) -#define SCALE_HANDLE_DISTANCE 25 -#define MOVE_HANDLE_DISTANCE 25 +constexpr real_t SCALE_HANDLE_DISTANCE = 25; +constexpr real_t MOVE_HANDLE_DISTANCE = 25; class SnapDialog : public ConfirmationDialog { GDCLASS(SnapDialog, ConfirmationDialog); @@ -110,6 +116,7 @@ public: grid_offset_x->set_allow_greater(true); grid_offset_x->set_suffix("px"); grid_offset_x->set_h_size_flags(Control::SIZE_EXPAND_FILL); + grid_offset_x->set_select_all_on_focus(true); child_container->add_child(grid_offset_x); grid_offset_y = memnew(SpinBox); @@ -119,6 +126,7 @@ public: grid_offset_y->set_allow_greater(true); grid_offset_y->set_suffix("px"); grid_offset_y->set_h_size_flags(Control::SIZE_EXPAND_FILL); + grid_offset_y->set_select_all_on_focus(true); child_container->add_child(grid_offset_y); label = memnew(Label); @@ -127,19 +135,21 @@ public: label->set_h_size_flags(Control::SIZE_EXPAND_FILL); grid_step_x = memnew(SpinBox); - grid_step_x->set_min(0.01); + grid_step_x->set_min(1); grid_step_x->set_max(SPIN_BOX_GRID_RANGE); grid_step_x->set_allow_greater(true); grid_step_x->set_suffix("px"); grid_step_x->set_h_size_flags(Control::SIZE_EXPAND_FILL); + grid_step_x->set_select_all_on_focus(true); child_container->add_child(grid_step_x); grid_step_y = memnew(SpinBox); - grid_step_y->set_min(0.01); + grid_step_y->set_min(1); grid_step_y->set_max(SPIN_BOX_GRID_RANGE); grid_step_y->set_allow_greater(true); grid_step_y->set_suffix("px"); grid_step_y->set_h_size_flags(Control::SIZE_EXPAND_FILL); + grid_step_y->set_select_all_on_focus(true); child_container->add_child(grid_step_y); child_container = memnew(GridContainer); @@ -158,6 +168,7 @@ public: primary_grid_steps->set_allow_greater(true); primary_grid_steps->set_suffix(TTR("steps")); primary_grid_steps->set_h_size_flags(Control::SIZE_EXPAND_FILL); + primary_grid_steps->set_select_all_on_focus(true); child_container->add_child(primary_grid_steps); container->add_child(memnew(HSeparator)); @@ -178,6 +189,7 @@ public: rotation_offset->set_max(SPIN_BOX_ROTATION_RANGE); rotation_offset->set_suffix("deg"); rotation_offset->set_h_size_flags(Control::SIZE_EXPAND_FILL); + rotation_offset->set_select_all_on_focus(true); child_container->add_child(rotation_offset); label = memnew(Label); @@ -190,6 +202,7 @@ public: rotation_step->set_max(SPIN_BOX_ROTATION_RANGE); rotation_step->set_suffix("deg"); rotation_step->set_h_size_flags(Control::SIZE_EXPAND_FILL); + rotation_step->set_select_all_on_focus(true); child_container->add_child(rotation_step); container->add_child(memnew(HSeparator)); @@ -208,6 +221,7 @@ public: scale_step->set_allow_greater(true); scale_step->set_h_size_flags(Control::SIZE_EXPAND_FILL); scale_step->set_step(0.01f); + scale_step->set_select_all_on_focus(true); child_container->add_child(scale_step); } @@ -217,8 +231,8 @@ public: grid_step_x->set_value(p_grid_step.x); grid_step_y->set_value(p_grid_step.y); primary_grid_steps->set_value(p_primary_grid_steps); - rotation_offset->set_value(Math::rad2deg(p_rotation_offset)); - rotation_step->set_value(Math::rad2deg(p_rotation_step)); + rotation_offset->set_value(Math::rad_to_deg(p_rotation_offset)); + rotation_step->set_value(Math::rad_to_deg(p_rotation_step)); scale_step->set_value(p_scale_step); } @@ -226,14 +240,14 @@ public: p_grid_offset = Point2(grid_offset_x->get_value(), grid_offset_y->get_value()); p_grid_step = Point2(grid_step_x->get_value(), grid_step_y->get_value()); p_primary_grid_steps = int(primary_grid_steps->get_value()); - p_rotation_offset = Math::deg2rad(rotation_offset->get_value()); - p_rotation_step = Math::deg2rad(rotation_step->get_value()); + p_rotation_offset = Math::deg_to_rad(rotation_offset->get_value()); + p_rotation_step = Math::deg_to_rad(rotation_step->get_value()); p_scale_step = scale_step->get_value(); } }; -bool CanvasItemEditor::_is_node_locked(const Node *p_node) { - return p_node->has_meta("_edit_lock_") && p_node->get_meta("_edit_lock_"); +bool CanvasItemEditor::_is_node_locked(const Node *p_node) const { + return p_node->get_meta("_edit_lock_", false); } bool CanvasItemEditor::_is_node_movable(const Node *p_node, bool p_popup_warning) { @@ -298,7 +312,7 @@ void CanvasItemEditor::_snap_other_nodes( Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2], const SnapTarget p_snap_target, List<const CanvasItem *> p_exceptions, const Node *p_current) { - const CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_current); + const CanvasItem *ci = Object::cast_to<CanvasItem>(p_current); // Check if the element is in the exception bool exception = false; @@ -309,12 +323,12 @@ void CanvasItemEditor::_snap_other_nodes( } }; - if (canvas_item && !exception) { - Transform2D ci_transform = canvas_item->get_global_transform_with_canvas(); + if (ci && !exception) { + Transform2D ci_transform = ci->get_global_transform_with_canvas(); if (fmod(ci_transform.get_rotation() - p_transform_to_snap.get_rotation(), (real_t)360.0) == 0.0) { - if (canvas_item->_edit_use_rect()) { - Point2 begin = ci_transform.xform(canvas_item->_edit_get_rect().get_position()); - Point2 end = ci_transform.xform(canvas_item->_edit_get_rect().get_position() + canvas_item->_edit_get_rect().get_size()); + if (ci->_edit_use_rect()) { + Point2 begin = ci_transform.xform(ci->_edit_get_rect().get_position()); + Point2 end = ci_transform.xform(ci->_edit_get_rect().get_position() + ci->_edit_get_rect().get_size()); _snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, begin, p_snap_target, ci_transform.get_rotation()); _snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, end, p_snap_target, ci_transform.get_rotation()); @@ -398,7 +412,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig // Other nodes sides if ((is_snap_active && snap_other_nodes && (p_modes & SNAP_OTHER_NODES)) || (p_forced_modes & SNAP_OTHER_NODES)) { - Transform2D to_snap_transform = Transform2D(); + Transform2D to_snap_transform; List<const CanvasItem *> exceptions = List<const CanvasItem *>(); for (const CanvasItem *E : p_other_nodes_exceptions) { exceptions.push_back(E); @@ -417,16 +431,14 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig } if (((is_snap_active && snap_guides && (p_modes & SNAP_GUIDES)) || (p_forced_modes & SNAP_GUIDES)) && fmod(rotation, (real_t)360.0) == 0.0) { - // Guides - if (EditorNode::get_singleton()->get_edited_scene() && EditorNode::get_singleton()->get_edited_scene()->has_meta("_edit_vertical_guides_")) { - Array vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_"); + // Guides. + if (Node *scene = EditorNode::get_singleton()->get_edited_scene()) { + Array vguides = scene->get_meta("_edit_vertical_guides_", Array()); for (int i = 0; i < vguides.size(); i++) { _snap_if_closer_float(p_target.x, output.x, snap_target[0], vguides[i], SNAP_TARGET_GUIDE); } - } - if (EditorNode::get_singleton()->get_edited_scene() && EditorNode::get_singleton()->get_edited_scene()->has_meta("_edit_horizontal_guides_")) { - Array hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_"); + Array hguides = scene->get_meta("_edit_horizontal_guides_", Array()); for (int i = 0; i < hguides.size(); i++) { _snap_if_closer_float(p_target.y, output.y, snap_target[1], hguides[i], SNAP_TARGET_GUIDE); } @@ -472,7 +484,7 @@ real_t CanvasItemEditor::snap_angle(real_t p_target, real_t p_start) const { } } -void CanvasItemEditor::unhandled_key_input(const Ref<InputEvent> &p_ev) { +void CanvasItemEditor::shortcut_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventKey> k = p_ev; @@ -483,21 +495,21 @@ void CanvasItemEditor::unhandled_key_input(const Ref<InputEvent> &p_ev) { if (k.is_valid()) { if (k->get_keycode() == Key::CTRL || k->get_keycode() == Key::ALT || k->get_keycode() == Key::SHIFT) { - viewport->update(); + viewport->queue_redraw(); } - if (k->is_pressed() && !k->is_ctrl_pressed() && !k->is_echo()) { - if ((grid_snap_active || show_grid) && multiply_grid_step_shortcut.is_valid() && multiply_grid_step_shortcut->matches_event(p_ev)) { + if (k->is_pressed() && !k->is_ctrl_pressed() && !k->is_echo() && (grid_snap_active || _is_grid_visible())) { + if (multiply_grid_step_shortcut.is_valid() && multiply_grid_step_shortcut->matches_event(p_ev)) { // Multiply the grid size grid_step_multiplier = MIN(grid_step_multiplier + 1, 12); - viewport->update(); - } else if ((grid_snap_active || show_grid) && divide_grid_step_shortcut.is_valid() && divide_grid_step_shortcut->matches_event(p_ev)) { + viewport->queue_redraw(); + } else if (divide_grid_step_shortcut.is_valid() && divide_grid_step_shortcut->matches_event(p_ev)) { // Divide the grid size Point2 new_grid_step = grid_step * Math::pow(2.0, grid_step_multiplier - 1); if (new_grid_step.x >= 1.0 && new_grid_step.y >= 1.0) { grid_step_multiplier--; } - viewport->update(); + viewport->queue_redraw(); } } } @@ -524,14 +536,14 @@ Rect2 CanvasItemEditor::_get_encompassing_rect_from_list(List<CanvasItem *> p_li ERR_FAIL_COND_V(p_list.is_empty(), Rect2()); // Handles the first element - CanvasItem *canvas_item = p_list.front()->get(); - Rect2 rect = Rect2(canvas_item->get_global_transform_with_canvas().xform(canvas_item->_edit_get_rect().get_center()), Size2()); + CanvasItem *ci = p_list.front()->get(); + Rect2 rect = Rect2(ci->get_global_transform_with_canvas().xform(ci->_edit_get_rect().get_center()), Size2()); // Expand with the other ones - for (CanvasItem *canvas_item2 : p_list) { - Transform2D xform = canvas_item2->get_global_transform_with_canvas(); + for (CanvasItem *ci2 : p_list) { + Transform2D xform = ci2->get_global_transform_with_canvas(); - Rect2 current_rect = canvas_item2->_edit_get_rect(); + Rect2 current_rect = ci2->_edit_get_rect(); rect.expand_to(xform.xform(current_rect.position)); rect.expand_to(xform.xform(current_rect.position + Vector2(current_rect.size.x, 0))); rect.expand_to(xform.xform(current_rect.position + current_rect.size)); @@ -549,20 +561,24 @@ void CanvasItemEditor::_expand_encompassing_rect_using_children(Rect2 &r_rect, c return; } - const CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); + const CanvasItem *ci = Object::cast_to<CanvasItem>(p_node); for (int i = p_node->get_child_count() - 1; i >= 0; i--) { - if (canvas_item && !canvas_item->is_set_as_top_level()) { - _expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); + if (ci && !ci->is_set_as_top_level()) { + _expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, p_parent_xform * ci->get_transform(), p_canvas_xform); } else { - const CanvasLayer *canvas_layer = Object::cast_to<CanvasLayer>(p_node); - _expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, Transform2D(), canvas_layer ? canvas_layer->get_transform() : p_canvas_xform); + const CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); + _expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, Transform2D(), cl ? cl->get_transform() : p_canvas_xform); } } - if (canvas_item && canvas_item->is_visible_in_tree() && (include_locked_nodes || !_is_node_locked(canvas_item))) { - Transform2D xform = p_parent_xform * p_canvas_xform * canvas_item->get_transform(); - Rect2 rect = canvas_item->_edit_get_rect(); + if (ci && ci->is_visible_in_tree() && (include_locked_nodes || !_is_node_locked(ci))) { + Transform2D xform = p_canvas_xform; + if (!ci->is_set_as_top_level()) { + xform *= p_parent_xform; + } + xform *= ci->get_transform(); + Rect2 rect = ci->_edit_get_rect(); if (r_first) { r_rect = Rect2(xform.xform(rect.get_center()), Size2()); r_first = false; @@ -591,14 +607,14 @@ void CanvasItemEditor::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_no } const real_t grab_distance = EDITOR_GET("editors/polygon_editor/point_grab_radius"); - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); + CanvasItem *ci = Object::cast_to<CanvasItem>(p_node); for (int i = p_node->get_child_count() - 1; i >= 0; i--) { - if (canvas_item) { - if (!canvas_item->is_set_as_top_level()) { - _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); + if (ci) { + if (!ci->is_set_as_top_level()) { + _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), p_canvas_xform); } else { - _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, canvas_item->get_transform(), p_canvas_xform); + _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, ci->get_transform(), p_canvas_xform); } } else { CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); @@ -606,14 +622,18 @@ void CanvasItemEditor::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_no } } - if (canvas_item && canvas_item->is_visible_in_tree()) { - Transform2D xform = (p_parent_xform * p_canvas_xform * canvas_item->get_transform()).affine_inverse(); + if (ci && ci->is_visible_in_tree()) { + Transform2D xform = p_canvas_xform; + if (!ci->is_set_as_top_level()) { + xform *= p_parent_xform; + } + xform = (xform * ci->get_transform()).affine_inverse(); const real_t local_grab_distance = xform.basis_xform(Vector2(grab_distance, 0)).length() / zoom; - if (canvas_item->_edit_is_selected_on_click(xform.xform(p_pos), local_grab_distance)) { - Node2D *node = Object::cast_to<Node2D>(canvas_item); + if (ci->_edit_is_selected_on_click(xform.xform(p_pos), local_grab_distance)) { + Node2D *node = Object::cast_to<Node2D>(ci); _SelectResult res; - res.item = canvas_item; + res.item = ci; res.z_index = node ? node->get_z_index() : 0; res.has_z = node; r_items.push_back(res); @@ -622,7 +642,7 @@ void CanvasItemEditor::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_no } void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_SelectResult> &r_items, bool p_allow_locked) { - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); _find_canvas_items_at_pos(p_pos, scene, r_items); @@ -635,13 +655,13 @@ void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_Sel node = scene->get_deepest_editable_node(node); } - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(node); + CanvasItem *ci = Object::cast_to<CanvasItem>(node); if (!p_allow_locked) { // Replace the node by the group if grouped while (node && node != scene->get_parent()) { - CanvasItem *canvas_item_tmp = Object::cast_to<CanvasItem>(node); - if (canvas_item_tmp && node->has_meta("_edit_group_")) { - canvas_item = canvas_item_tmp; + CanvasItem *ci_tmp = Object::cast_to<CanvasItem>(node); + if (ci_tmp && node->has_meta("_edit_group_")) { + ci = ci_tmp; } node = node->get_parent(); } @@ -650,18 +670,18 @@ void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_Sel // Check if the canvas item is already in the list (for groups or scenes) bool duplicate = false; for (int j = 0; j < i; j++) { - if (r_items[j].item == canvas_item) { + if (r_items[j].item == ci) { duplicate = true; break; } } //Remove the item if invalid - if (!canvas_item || duplicate || (canvas_item != scene && canvas_item->get_owner() != scene && !scene->is_editable_instance(canvas_item->get_owner())) || (!p_allow_locked && _is_node_locked(canvas_item))) { + if (!ci || duplicate || (ci != scene && ci->get_owner() != scene && !scene->is_editable_instance(ci->get_owner())) || (!p_allow_locked && _is_node_locked(ci))) { r_items.remove_at(i); i--; } else { - r_items.write[i].item = canvas_item; + r_items.write[i].item = ci; } } } @@ -674,42 +694,46 @@ void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_n return; } - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); - Node *scene = editor->get_edited_scene(); + CanvasItem *ci = Object::cast_to<CanvasItem>(p_node); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); bool editable = p_node == scene || p_node->get_owner() == scene || p_node == scene->get_deepest_editable_node(p_node); - bool lock_children = p_node->has_meta("_edit_group_") && p_node->get_meta("_edit_group_"); + bool lock_children = p_node->get_meta("_edit_group_", false); bool locked = _is_node_locked(p_node); if (!lock_children || !editable) { for (int i = p_node->get_child_count() - 1; i >= 0; i--) { - if (canvas_item) { - if (!canvas_item->is_set_as_top_level()) { - _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); + if (ci) { + if (!ci->is_set_as_top_level()) { + _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), p_canvas_xform); } else { - _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, canvas_item->get_transform(), p_canvas_xform); + _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, ci->get_transform(), p_canvas_xform); } } else { - CanvasLayer *canvas_layer = Object::cast_to<CanvasLayer>(p_node); - _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, Transform2D(), canvas_layer ? canvas_layer->get_transform() : p_canvas_xform); + CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); + _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, Transform2D(), cl ? cl->get_transform() : p_canvas_xform); } } } - if (canvas_item && canvas_item->is_visible_in_tree() && !locked && editable) { - Transform2D xform = p_parent_xform * p_canvas_xform * canvas_item->get_transform(); + if (ci && ci->is_visible_in_tree() && !locked && editable) { + Transform2D xform = p_canvas_xform; + if (!ci->is_set_as_top_level()) { + xform *= p_parent_xform; + } + xform *= ci->get_transform(); - if (canvas_item->_edit_use_rect()) { - Rect2 rect = canvas_item->_edit_get_rect(); + if (ci->_edit_use_rect()) { + Rect2 rect = ci->_edit_get_rect(); if (p_rect.has_point(xform.xform(rect.position)) && p_rect.has_point(xform.xform(rect.position + Vector2(rect.size.x, 0))) && p_rect.has_point(xform.xform(rect.position + Vector2(rect.size.x, rect.size.y))) && p_rect.has_point(xform.xform(rect.position + Vector2(0, rect.size.y)))) { - r_items->push_back(canvas_item); + r_items->push_back(ci); } } else { if (p_rect.has_point(xform.xform(Point2()))) { - r_items->push_back(canvas_item); + r_items->push_back(ci); } } } @@ -724,7 +748,7 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po still_selected = false; if (editor_selection->get_selected_node_list().size() == 1) { - editor->push_item(editor_selection->get_selected_node_list()[0]); + EditorNode::get_singleton()->push_item(editor_selection->get_selected_node_list()[0]); } } else { // Add the item to the selection @@ -738,22 +762,22 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po // Reselect if (Engine::get_singleton()->is_editor_hint()) { selected_from_canvas = true; - editor->call("edit_node", item); + EditorNode::get_singleton()->edit_node(item); } } } - viewport->update(); + viewport->queue_redraw(); return still_selected; } -List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retreive_locked, bool remove_canvas_item_if_parent_in_selection) { +List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retrieve_locked, bool remove_canvas_item_if_parent_in_selection) const { List<CanvasItem *> selection; for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); - if (canvas_item && canvas_item->is_visible_in_tree() && canvas_item->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retreive_locked || !_is_node_locked(canvas_item))) { - CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); + CanvasItem *ci = Object::cast_to<CanvasItem>(E.key); + if (ci && ci->is_visible_in_tree() && ci->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retrieve_locked || !_is_node_locked(ci))) { + CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci); if (se) { - selection.push_back(canvas_item); + selection.push_back(ci); } } } @@ -789,7 +813,7 @@ Vector2 CanvasItemEditor::_position_to_anchor(const Control *p_control, Vector2 Rect2 parent_rect = p_control->get_parent_anchorable_rect(); - Vector2 output = Vector2(); + Vector2 output; if (p_control->is_layout_rtl()) { output.x = (parent_rect.size.x == 0) ? 0.0 : (parent_rect.size.x - p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; } else { @@ -800,13 +824,21 @@ Vector2 CanvasItemEditor::_position_to_anchor(const Control *p_control, Vector2 } void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones) { - for (CanvasItem *canvas_item : p_canvas_items) { - CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); + original_transform = Transform2D(); + bool transform_stored = false; + + for (CanvasItem *ci : p_canvas_items) { + CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci); if (se) { - se->undo_state = canvas_item->_edit_get_state(); - se->pre_drag_xform = canvas_item->get_global_transform_with_canvas(); - if (canvas_item->_edit_use_rect()) { - se->pre_drag_rect = canvas_item->_edit_get_rect(); + if (!transform_stored) { + original_transform = ci->get_global_transform(); + transform_stored = true; + } + + se->undo_state = ci->_edit_get_state(); + se->pre_drag_xform = ci->get_global_transform_with_canvas(); + if (ci->_edit_use_rect()) { + se->pre_drag_rect = ci->_edit_get_rect(); } else { se->pre_drag_rect = Rect2(); } @@ -815,20 +847,20 @@ void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items } void CanvasItemEditor::_restore_canvas_item_state(List<CanvasItem *> p_canvas_items, bool restore_bones) { - for (CanvasItem *canvas_item : drag_selection) { - CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); - canvas_item->_edit_set_state(se->undo_state); + for (CanvasItem *ci : drag_selection) { + CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci); + ci->_edit_set_state(se->undo_state); } } void CanvasItemEditor::_commit_canvas_item_state(List<CanvasItem *> p_canvas_items, String action_name, bool commit_bones) { List<CanvasItem *> modified_canvas_items; - for (CanvasItem *canvas_item : p_canvas_items) { - Dictionary old_state = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item)->undo_state; - Dictionary new_state = canvas_item->_edit_get_state(); + for (CanvasItem *ci : p_canvas_items) { + Dictionary old_state = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci)->undo_state; + Dictionary new_state = ci->_edit_get_state(); if (old_state.hash() != new_state.hash()) { - modified_canvas_items.push_back(canvas_item); + modified_canvas_items.push_back(ci); } } @@ -836,30 +868,31 @@ void CanvasItemEditor::_commit_canvas_item_state(List<CanvasItem *> p_canvas_ite return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(action_name); - for (CanvasItem *canvas_item : modified_canvas_items) { - CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); + for (CanvasItem *ci : modified_canvas_items) { + CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci); if (se) { - undo_redo->add_do_method(canvas_item, "_edit_set_state", canvas_item->_edit_get_state()); - undo_redo->add_undo_method(canvas_item, "_edit_set_state", se->undo_state); + undo_redo->add_do_method(ci, "_edit_set_state", ci->_edit_get_state()); + undo_redo->add_undo_method(ci, "_edit_set_state", se->undo_state); if (commit_bones) { for (const Dictionary &F : se->pre_drag_bones_undo_state) { - canvas_item = Object::cast_to<CanvasItem>(canvas_item->get_parent()); - undo_redo->add_do_method(canvas_item, "_edit_set_state", canvas_item->_edit_get_state()); - undo_redo->add_undo_method(canvas_item, "_edit_set_state", F); + ci = Object::cast_to<CanvasItem>(ci->get_parent()); + undo_redo->add_do_method(ci, "_edit_set_state", ci->_edit_get_state()); + undo_redo->add_undo_method(ci, "_edit_set_state", F); } } } } - undo_redo->add_do_method(viewport, "update"); - undo_redo->add_undo_method(viewport, "update"); + undo_redo->add_do_method(viewport, "queue_redraw"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } void CanvasItemEditor::_snap_changed() { - ((SnapDialog *)snap_dialog)->get_fields(grid_offset, grid_step, primary_grid_steps, snap_rotation_offset, snap_rotation_step, snap_scale_step); + static_cast<SnapDialog *>(snap_dialog)->get_fields(grid_offset, grid_step, primary_grid_steps, snap_rotation_offset, snap_rotation_step, snap_scale_step); grid_step_multiplier = 0; - viewport->update(); + viewport->queue_redraw(); } void CanvasItemEditor::_selection_result_pressed(int p_result) { @@ -881,10 +914,38 @@ void CanvasItemEditor::_selection_menu_hide() { } void CanvasItemEditor::_add_node_pressed(int p_result) { - if (p_result == AddNodeOption::ADD_NODE) { - editor->get_scene_tree_dock()->open_add_child_dialog(); - } else if (p_result == AddNodeOption::ADD_INSTANCE) { - editor->get_scene_tree_dock()->open_instance_child_dialog(); + List<Node *> nodes_to_move; + + switch (p_result) { + case ADD_NODE: { + SceneTreeDock::get_singleton()->open_add_child_dialog(); + } break; + case ADD_INSTANCE: { + SceneTreeDock::get_singleton()->open_instance_child_dialog(); + } break; + case ADD_PASTE: { + nodes_to_move = SceneTreeDock::get_singleton()->paste_nodes(); + [[fallthrough]]; + } + case ADD_MOVE: { + nodes_to_move = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list(); + if (nodes_to_move.is_empty()) { + return; + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Move Node(s) to Position")); + for (Node *node : nodes_to_move) { + CanvasItem *ci = Object::cast_to<CanvasItem>(node); + if (ci) { + Transform2D xform = ci->get_global_transform_with_canvas().affine_inverse() * ci->get_transform(); + undo_redo->add_do_method(ci, "_edit_set_position", xform.xform(node_create_position)); + undo_redo->add_undo_method(ci, "_edit_set_position", ci->_edit_get_position()); + } + } + undo_redo->commit_action(); + _reset_create_position(); + } break; } } @@ -906,7 +967,62 @@ void CanvasItemEditor::_reset_create_position() { node_create_position = Point2(); } +bool CanvasItemEditor::_is_grid_visible() const { + switch (grid_visibility) { + case GRID_VISIBILITY_SHOW: + return true; + case GRID_VISIBILITY_SHOW_WHEN_SNAPPING: + return grid_snap_active; + case GRID_VISIBILITY_HIDE: + return false; + } + ERR_FAIL_V_MSG(true, "Unexpected grid_visibility value"); +} + +void CanvasItemEditor::_prepare_grid_menu() { + for (int i = GRID_VISIBILITY_SHOW; i <= GRID_VISIBILITY_HIDE; i++) { + grid_menu->set_item_checked(i, i == grid_visibility); + } +} + +void CanvasItemEditor::_on_grid_menu_id_pressed(int p_id) { + switch (p_id) { + case GRID_VISIBILITY_SHOW: + case GRID_VISIBILITY_SHOW_WHEN_SNAPPING: + case GRID_VISIBILITY_HIDE: + grid_visibility = (GridVisibility)p_id; + viewport->queue_redraw(); + view_menu->get_popup()->hide(); + return; + } + + // Toggle grid: go to the least restrictive option possible. + if (grid_snap_active) { + switch (grid_visibility) { + case GRID_VISIBILITY_SHOW: + case GRID_VISIBILITY_SHOW_WHEN_SNAPPING: + grid_visibility = GRID_VISIBILITY_HIDE; + break; + case GRID_VISIBILITY_HIDE: + grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING; + break; + } + } else { + switch (grid_visibility) { + case GRID_VISIBILITY_SHOW: + grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING; + break; + case GRID_VISIBILITY_SHOW_WHEN_SNAPPING: + case GRID_VISIBILITY_HIDE: + grid_visibility = GRID_VISIBILITY_SHOW; + break; + } + } + viewport->queue_redraw(); +} + bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_event) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); Ref<InputEventMouseButton> b = p_event; Ref<InputEventMouseMotion> m = p_event; @@ -914,14 +1030,8 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve if (show_guides && show_rulers && EditorNode::get_singleton()->get_edited_scene()) { Transform2D xform = viewport_scrollable->get_transform() * transform; // Retrieve the guide lists - Array vguides; - if (EditorNode::get_singleton()->get_edited_scene()->has_meta("_edit_vertical_guides_")) { - vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_"); - } - Array hguides; - if (EditorNode::get_singleton()->get_edited_scene()->has_meta("_edit_horizontal_guides_")) { - hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_"); - } + Array vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_", Array()); + Array hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_", Array()); // Hover over guides real_t minimum = 1e20; @@ -1004,7 +1114,7 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve drag_to = xform.affine_inverse().xform(m->get_position()); dragged_guide_pos = xform.xform(snap_point(drag_to, SNAP_GRID | SNAP_PIXEL | SNAP_OTHER_NODES)); - viewport->update(); + viewport->queue_redraw(); return true; } @@ -1014,14 +1124,8 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve Transform2D xform = viewport_scrollable->get_transform() * transform; // Retrieve the guide lists - Array vguides; - if (EditorNode::get_singleton()->get_edited_scene()->has_meta("_edit_vertical_guides_")) { - vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_"); - } - Array hguides; - if (EditorNode::get_singleton()->get_edited_scene()->has_meta("_edit_horizontal_guides_")) { - hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_"); - } + Array vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_", Array()); + Array hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_", Array()); Point2 edited = snap_point(xform.affine_inverse().xform(b->get_position()), SNAP_GRID | SNAP_PIXEL | SNAP_OTHER_NODES); if (drag_type == DRAG_V_GUIDE) { @@ -1033,14 +1137,14 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve undo_redo->create_action(TTR("Move Vertical Guide")); undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides); undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides); - undo_redo->add_undo_method(viewport, "update"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } else { vguides.push_back(edited.x); undo_redo->create_action(TTR("Create Vertical Guide")); undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides); undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides); - undo_redo->add_undo_method(viewport, "update"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } } else { @@ -1053,7 +1157,7 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides); } undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides); - undo_redo->add_undo_method(viewport, "update"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } } @@ -1066,14 +1170,14 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve undo_redo->create_action(TTR("Move Horizontal Guide")); undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides); undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides); - undo_redo->add_undo_method(viewport, "update"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } else { hguides.push_back(edited.y); undo_redo->create_action(TTR("Create Horizontal Guide")); undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides); undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides); - undo_redo->add_undo_method(viewport, "update"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } } else { @@ -1086,7 +1190,7 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides); } undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides); - undo_redo->add_undo_method(viewport, "update"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } } @@ -1102,13 +1206,15 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides); undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides); undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides); - undo_redo->add_undo_method(viewport, "update"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } } } - drag_type = DRAG_NONE; - viewport->update(); + snap_target[0] = SNAP_TARGET_NONE; + snap_target[1] = SNAP_TARGET_NONE; + _reset_drag(); + viewport->queue_redraw(); return true; } } @@ -1116,175 +1222,63 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve } bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bool p_already_accepted) { - Ref<InputEventMouseButton> b = p_event; - if (b.is_valid() && !p_already_accepted) { - const bool pan_on_scroll = bool(EditorSettings::get_singleton()->get("editors/2d/scroll_to_pan")) && !b->is_ctrl_pressed(); - - if (pan_on_scroll) { - // Perform horizontal scrolling first so we can check for Shift being held. - if (b->is_pressed() && - (b->get_button_index() == MouseButton::WHEEL_LEFT || (b->is_shift_pressed() && b->get_button_index() == MouseButton::WHEEL_UP))) { - // Pan left - view_offset.x -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); - update_viewport(); - return true; - } - - if (b->is_pressed() && - (b->get_button_index() == MouseButton::WHEEL_RIGHT || (b->is_shift_pressed() && b->get_button_index() == MouseButton::WHEEL_DOWN))) { - // Pan right - view_offset.x += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); - update_viewport(); - return true; - } - } - - if (b->is_pressed() && b->get_button_index() == MouseButton::WHEEL_DOWN) { - // Scroll or pan down - if (pan_on_scroll) { - view_offset.y += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); - update_viewport(); - } else { - zoom_widget->set_zoom_by_increments(-1, Input::get_singleton()->is_key_pressed(Key::ALT)); - if (!Math::is_equal_approx(b->get_factor(), 1.0f)) { - // Handle high-precision (analog) scrolling. - zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * b->get_factor() + 1.f)); - } - _zoom_on_position(zoom_widget->get_zoom(), b->get_position()); - } - return true; - } - - if (b->is_pressed() && b->get_button_index() == MouseButton::WHEEL_UP) { - // Scroll or pan up - if (pan_on_scroll) { - view_offset.y -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); - update_viewport(); - } else { - zoom_widget->set_zoom_by_increments(1, Input::get_singleton()->is_key_pressed(Key::ALT)); - if (!Math::is_equal_approx(b->get_factor(), 1.0f)) { - // Handle high-precision (analog) scrolling. - zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * b->get_factor() + 1.f)); - } - _zoom_on_position(zoom_widget->get_zoom(), b->get_position()); - } - return true; - } - - if (!panning) { - if (b->is_pressed() && - (b->get_button_index() == MouseButton::MIDDLE || - (b->get_button_index() == MouseButton::LEFT && tool == TOOL_PAN) || - (b->get_button_index() == MouseButton::LEFT && !EditorSettings::get_singleton()->get("editors/2d/simple_panning") && pan_pressed))) { - // Pan the viewport - panning = true; - } - } + panner->set_force_drag(tool == TOOL_PAN); + bool panner_active = panner->gui_input(p_event, warped_panning ? viewport->get_global_rect() : Rect2()); + if (panner->is_panning() != pan_pressed) { + pan_pressed = panner->is_panning(); + } - if (panning) { - if (!b->is_pressed() && (pan_on_scroll || (b->get_button_index() != MouseButton::WHEEL_DOWN && b->get_button_index() != MouseButton::WHEEL_UP))) { - // Stop panning the viewport (for any mouse button press except zooming) - panning = false; - } - } + if (panner_active) { + return true; } Ref<InputEventKey> k = p_event; if (k.is_valid()) { if (k->is_pressed()) { if (ED_GET_SHORTCUT("canvas_item_editor/zoom_3.125_percent")->matches_event(p_event)) { - _update_zoom((1.0 / 32.0) * MAX(1, EDSCALE)); + _shortcut_zoom_set(1.0 / 32.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_6.25_percent")->matches_event(p_event)) { - _update_zoom((1.0 / 16.0) * MAX(1, EDSCALE)); + _shortcut_zoom_set(1.0 / 16.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_12.5_percent")->matches_event(p_event)) { - _update_zoom((1.0 / 8.0) * MAX(1, EDSCALE)); + _shortcut_zoom_set(1.0 / 8.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_25_percent")->matches_event(p_event)) { - _update_zoom((1.0 / 4.0) * MAX(1, EDSCALE)); + _shortcut_zoom_set(1.0 / 4.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_50_percent")->matches_event(p_event)) { - _update_zoom((1.0 / 2.0) * MAX(1, EDSCALE)); + _shortcut_zoom_set(1.0 / 2.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_100_percent")->matches_event(p_event)) { - _update_zoom(1.0 * MAX(1, EDSCALE)); + _shortcut_zoom_set(1.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_200_percent")->matches_event(p_event)) { - _update_zoom(2.0 * MAX(1, EDSCALE)); + _shortcut_zoom_set(2.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_400_percent")->matches_event(p_event)) { - _update_zoom(4.0 * MAX(1, EDSCALE)); + _shortcut_zoom_set(4.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_800_percent")->matches_event(p_event)) { - _update_zoom(8.0 * MAX(1, EDSCALE)); + _shortcut_zoom_set(8.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_1600_percent")->matches_event(p_event)) { - _update_zoom(16.0 * MAX(1, EDSCALE)); - } - } - - bool is_pan_key = pan_view_shortcut.is_valid() && pan_view_shortcut->matches_event(p_event); - - if (is_pan_key && (EditorSettings::get_singleton()->get("editors/2d/simple_panning") || drag_type != DRAG_NONE)) { - if (!panning) { - if (k->is_pressed() && !k->is_echo()) { - //Pan the viewport - panning = true; - } - } else { - if (!k->is_pressed()) { - // Stop panning the viewport (for any mouse button press) - panning = false; - } - } - } - - if (is_pan_key && pan_pressed != k->is_pressed()) { - pan_pressed = k->is_pressed(); - _update_cursor(); - } - } - - Ref<InputEventMouseMotion> m = p_event; - if (m.is_valid()) { - if (panning) { - // Pan the viewport - Point2i relative; - if (bool(EditorSettings::get_singleton()->get("editors/2d/warped_mouse_panning"))) { - relative = Input::get_singleton()->warp_mouse_motion(m, viewport->get_global_rect()); - } else { - relative = m->get_relative(); + _shortcut_zoom_set(16.0); } - view_offset.x -= relative.x / zoom; - view_offset.y -= relative.y / zoom; - update_viewport(); - return true; } } - Ref<InputEventMagnifyGesture> magnify_gesture = p_event; - if (magnify_gesture.is_valid() && !p_already_accepted) { - // Zoom gesture - _zoom_on_position(zoom * magnify_gesture->get_factor(), magnify_gesture->get_position()); - return true; - } - - Ref<InputEventPanGesture> pan_gesture = p_event; - if (pan_gesture.is_valid() && !p_already_accepted) { - // If ctrl key pressed, then zoom instead of pan. - if (pan_gesture->is_ctrl_pressed()) { - const real_t factor = pan_gesture->get_delta().y; - - zoom_widget->set_zoom_by_increments(1); - if (factor != 1.f) { - zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * factor + 1.f)); - } - _zoom_on_position(zoom_widget->get_zoom(), pan_gesture->get_position()); + return false; +} - return true; - } +void CanvasItemEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) { + view_offset.x -= p_scroll_vec.x / zoom; + view_offset.y -= p_scroll_vec.y / zoom; + update_viewport(); +} - // Pan gesture - const Vector2 delta = (int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom) * pan_gesture->get_delta(); - view_offset.x += delta.x; - view_offset.y += delta.y; - update_viewport(); - return true; +void CanvasItemEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) { + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + // Special behvior for scroll events, as the zoom_by_increment method can smartly end up on powers of two. + int increment = p_zoom_factor > 1.0 ? 1 : -1; + zoom_widget->set_zoom_by_increments(increment, mb->is_alt_pressed()); + } else { + zoom_widget->set_zoom(zoom_widget->get_zoom() * p_zoom_factor); } - return false; + _zoom_on_position(zoom_widget->get_zoom(), p_origin); } bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { @@ -1295,14 +1289,14 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { // Drag the pivot (in pivot mode / with V key) if (drag_type == DRAG_NONE) { if ((b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) || - (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == Key::V && tool == TOOL_SELECT && k->get_modifiers_mask() == Key::NONE)) { + (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == Key::V && tool == TOOL_SELECT && k->get_modifiers_mask().is_empty())) { List<CanvasItem *> selection = _get_edited_canvas_items(); // Filters the selection with nodes that allow setting the pivot drag_selection = List<CanvasItem *>(); - for (CanvasItem *canvas_item : selection) { - if (canvas_item->_edit_use_pivot()) { - drag_selection.push_back(canvas_item); + for (CanvasItem *ci : selection) { + if (ci->_edit_use_pivot()) { + drag_selection.push_back(ci); } } @@ -1316,8 +1310,8 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { } else { new_pos = snap_point(drag_from, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, nullptr, drag_selection); } - for (CanvasItem *canvas_item : drag_selection) { - canvas_item->_edit_set_pivot(canvas_item->get_global_transform_with_canvas().affine_inverse().xform(new_pos)); + for (CanvasItem *ci : drag_selection) { + ci->_edit_set_pivot(ci->get_global_transform_with_canvas().affine_inverse().xform(new_pos)); } drag_type = DRAG_PIVOT; @@ -1337,8 +1331,8 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { } else { new_pos = snap_point(drag_to, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL); } - for (CanvasItem *canvas_item : drag_selection) { - canvas_item->_edit_set_pivot(canvas_item->get_global_transform_with_canvas().affine_inverse().xform(new_pos)); + for (CanvasItem *ci : drag_selection) { + ci->_edit_set_pivot(ci->get_global_transform_with_canvas().affine_inverse().xform(new_pos)); } return true; } @@ -1354,15 +1348,15 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { drag_selection[0]->get_name(), drag_selection[0]->_edit_get_pivot().x, drag_selection[0]->_edit_get_pivot().y)); - drag_type = DRAG_NONE; + _reset_drag(); return true; } // Cancel a drag if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) { _restore_canvas_item_state(drag_selection); - drag_type = DRAG_NONE; - viewport->update(); + _reset_drag(); + viewport->queue_redraw(); return true; } } @@ -1376,7 +1370,7 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { // Start rotation if (drag_type == DRAG_NONE) { if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) { - if ((b->is_command_pressed() && !b->is_alt_pressed() && tool == TOOL_SELECT) || tool == TOOL_ROTATE) { + if ((b->is_command_or_control_pressed() && !b->is_alt_pressed() && tool == TOOL_SELECT) || tool == TOOL_ROTATE) { List<CanvasItem *> selection = _get_edited_canvas_items(); // Remove not movable nodes @@ -1390,11 +1384,11 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { if (drag_selection.size() > 0) { drag_type = DRAG_ROTATE; drag_from = transform.affine_inverse().xform(b->get_position()); - CanvasItem *canvas_item = drag_selection[0]; - if (canvas_item->_edit_use_pivot()) { - drag_rotation_center = canvas_item->get_global_transform_with_canvas().xform(canvas_item->_edit_get_pivot()); + CanvasItem *ci = drag_selection[0]; + if (ci->_edit_use_pivot()) { + drag_rotation_center = ci->get_global_transform_with_canvas().xform(ci->_edit_get_pivot()); } else { - drag_rotation_center = canvas_item->get_global_transform_with_canvas().get_origin(); + drag_rotation_center = ci->get_global_transform_with_canvas().get_origin(); } _save_canvas_item_state(drag_selection); return true; @@ -1407,12 +1401,12 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { // Rotate the node if (m.is_valid()) { _restore_canvas_item_state(drag_selection); - for (CanvasItem *canvas_item : drag_selection) { + for (CanvasItem *ci : drag_selection) { drag_to = transform.affine_inverse().xform(m->get_position()); //Rotate the opposite way if the canvas item's compounded scale has an uneven number of negative elements - bool opposite = (canvas_item->get_global_transform().get_scale().sign().dot(canvas_item->get_transform().get_scale().sign()) == 0); - canvas_item->_edit_set_rotation(snap_angle(canvas_item->_edit_get_rotation() + (opposite ? -1 : 1) * (drag_from - drag_rotation_center).angle_to(drag_to - drag_rotation_center), canvas_item->_edit_get_rotation())); - viewport->update(); + bool opposite = (ci->get_global_transform().get_scale().sign().dot(ci->get_transform().get_scale().sign()) == 0); + ci->_edit_set_rotation(snap_angle(ci->_edit_get_rotation() + (opposite ? -1 : 1) * (drag_from - drag_rotation_center).angle_to(drag_to - drag_rotation_center), ci->_edit_get_rotation())); + viewport->queue_redraw(); } return true; } @@ -1429,7 +1423,7 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { drag_selection, vformat(TTR("Rotate CanvasItem \"%s\" to %d degrees"), drag_selection[0]->get_name(), - Math::rad2deg(drag_selection[0]->_edit_get_rotation())), + Math::rad_to_deg(drag_selection[0]->_edit_get_rotation())), true); } @@ -1437,15 +1431,15 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { _insert_animation_keys(false, true, false, true); } - drag_type = DRAG_NONE; + _reset_drag(); return true; } // Cancel a drag if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) { _restore_canvas_item_state(drag_selection); - drag_type = DRAG_NONE; - viewport->update(); + _reset_drag(); + viewport->queue_redraw(); return true; } } @@ -1459,9 +1453,9 @@ bool CanvasItemEditor::_gui_input_open_scene_on_double_click(const Ref<InputEven if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && b->is_double_click() && tool == TOOL_SELECT) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { - CanvasItem *canvas_item = selection[0]; - if (!canvas_item->get_scene_file_path().is_empty() && canvas_item != editor->get_edited_scene()) { - editor->open_request(canvas_item->get_scene_file_path()); + CanvasItem *ci = selection[0]; + if (!ci->get_scene_file_path().is_empty() && ci != EditorNode::get_singleton()->get_edited_scene()) { + EditorNode::get_singleton()->open_request(ci->get_scene_file_path()); return true; } } @@ -1497,7 +1491,7 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) { } } - DragType dragger[] = { + const DragType dragger[] = { DRAG_ANCHOR_TOP_LEFT, DRAG_ANCHOR_TOP_RIGHT, DRAG_ANCHOR_BOTTOM_RIGHT, @@ -1599,15 +1593,15 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) { _commit_canvas_item_state( drag_selection, vformat(TTR("Move CanvasItem \"%s\" Anchor"), drag_selection[0]->get_name())); - drag_type = DRAG_NONE; + _reset_drag(); return true; } // Cancel a drag if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) { _restore_canvas_item_state(drag_selection); - drag_type = DRAG_NONE; - viewport->update(); + _reset_drag(); + viewport->queue_redraw(); return true; } } @@ -1623,19 +1617,19 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { - CanvasItem *canvas_item = selection[0]; - if (canvas_item->_edit_use_rect() && _is_node_movable(canvas_item)) { - Rect2 rect = canvas_item->_edit_get_rect(); - Transform2D xform = transform * canvas_item->get_global_transform_with_canvas(); + CanvasItem *ci = selection[0]; + if (ci->_edit_use_rect() && _is_node_movable(ci)) { + Rect2 rect = ci->_edit_get_rect(); + Transform2D xform = transform * ci->get_global_transform_with_canvas(); - Vector2 endpoints[4] = { + const Vector2 endpoints[4] = { xform.xform(rect.position), xform.xform(rect.position + Vector2(rect.size.x, 0)), xform.xform(rect.position + rect.size), xform.xform(rect.position + Vector2(0, rect.size.y)) }; - DragType dragger[] = { + const DragType dragger[] = { DRAG_TOP_LEFT, DRAG_TOP, DRAG_TOP_RIGHT, @@ -1671,7 +1665,7 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { drag_type = resize_drag; drag_from = transform.affine_inverse().xform(b->get_position()); drag_selection = List<CanvasItem *>(); - drag_selection.push_back(canvas_item); + drag_selection.push_back(ci); _save_canvas_item_state(drag_selection); return true; } @@ -1684,40 +1678,34 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT) { // Resize the node if (m.is_valid()) { - CanvasItem *canvas_item = drag_selection[0]; - CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); + CanvasItem *ci = drag_selection[0]; + CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci); //Reset state - canvas_item->_edit_set_state(se->undo_state); + ci->_edit_set_state(se->undo_state); bool uniform = m->is_shift_pressed(); bool symmetric = m->is_alt_pressed(); - Rect2 local_rect = canvas_item->_edit_get_rect(); + Rect2 local_rect = ci->_edit_get_rect(); real_t aspect = local_rect.get_size().y / local_rect.get_size().x; Point2 current_begin = local_rect.get_position(); Point2 current_end = local_rect.get_position() + local_rect.get_size(); - Point2 max_begin = (symmetric) ? (current_begin + current_end - canvas_item->_edit_get_minimum_size()) / 2.0 : current_end - canvas_item->_edit_get_minimum_size(); - Point2 min_end = (symmetric) ? (current_begin + current_end + canvas_item->_edit_get_minimum_size()) / 2.0 : current_begin + canvas_item->_edit_get_minimum_size(); + Point2 max_begin = (symmetric) ? (current_begin + current_end - ci->_edit_get_minimum_size()) / 2.0 : current_end - ci->_edit_get_minimum_size(); + Point2 min_end = (symmetric) ? (current_begin + current_end + ci->_edit_get_minimum_size()) / 2.0 : current_begin + ci->_edit_get_minimum_size(); Point2 center = (current_begin + current_end) / 2.0; drag_to = transform.affine_inverse().xform(m->get_position()); - Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse(); + Transform2D xform = ci->get_global_transform_with_canvas(); Point2 drag_to_snapped_begin; Point2 drag_to_snapped_end; - // last call decides which snapping lines are drawn - if (drag_type == DRAG_LEFT || drag_type == DRAG_TOP || drag_type == DRAG_TOP_LEFT) { - drag_to_snapped_end = snap_point(xform.affine_inverse().xform(current_end) + (drag_to - drag_from), SNAP_NODE_ANCHORS | SNAP_NODE_PARENT | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, canvas_item); - drag_to_snapped_begin = snap_point(xform.affine_inverse().xform(current_begin) + (drag_to - drag_from), SNAP_NODE_ANCHORS | SNAP_NODE_PARENT | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, canvas_item); - } else { - drag_to_snapped_begin = snap_point(xform.affine_inverse().xform(current_begin) + (drag_to - drag_from), SNAP_NODE_ANCHORS | SNAP_NODE_PARENT | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, canvas_item); - drag_to_snapped_end = snap_point(xform.affine_inverse().xform(current_end) + (drag_to - drag_from), SNAP_NODE_ANCHORS | SNAP_NODE_PARENT | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, canvas_item); - } + drag_to_snapped_end = snap_point(xform.xform(current_end) + (drag_to - drag_from), SNAP_NODE_ANCHORS | SNAP_NODE_PARENT | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, ci); + drag_to_snapped_begin = snap_point(xform.xform(current_begin) + (drag_to - drag_from), SNAP_NODE_ANCHORS | SNAP_NODE_PARENT | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, ci); - Point2 drag_begin = xform.xform(drag_to_snapped_begin); - Point2 drag_end = xform.xform(drag_to_snapped_end); + Point2 drag_begin = xform.affine_inverse().xform(drag_to_snapped_begin); + Point2 drag_end = xform.affine_inverse().xform(drag_to_snapped_end); // Horizontal resize if (drag_type == DRAG_LEFT || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_BOTTOM_LEFT) { @@ -1769,7 +1757,7 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { current_begin.y = 2.0 * center.y - current_end.y; } } - canvas_item->_edit_set_rect(Rect2(current_begin, current_end - current_begin)); + ci->_edit_set_rect(Rect2(current_begin, current_end - current_begin)); return true; } @@ -1805,8 +1793,8 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; - drag_type = DRAG_NONE; - viewport->update(); + _reset_drag(); + viewport->queue_redraw(); return true; } @@ -1815,8 +1803,8 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { _restore_canvas_item_state(drag_selection); snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; - drag_type = DRAG_NONE; - viewport->update(); + _reset_drag(); + viewport->queue_redraw(); return true; } } @@ -1829,14 +1817,14 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { // Drag resize handles if (drag_type == DRAG_NONE) { - if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && ((b->is_alt_pressed() && b->is_ctrl_pressed()) || tool == TOOL_SCALE)) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && ((b->is_alt_pressed() && b->is_command_or_control_pressed()) || tool == TOOL_SCALE)) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { - CanvasItem *canvas_item = selection[0]; + CanvasItem *ci = selection[0]; - if (_is_node_movable(canvas_item)) { - Transform2D xform = transform * canvas_item->get_global_transform_with_canvas(); - Transform2D unscaled_transform = (xform * canvas_item->get_transform().affine_inverse() * canvas_item->_edit_get_transform()).orthonormalized(); + if (_is_node_movable(ci)) { + Transform2D xform = transform * ci->get_global_transform_with_canvas(); + Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized(); Transform2D simple_xform = viewport->get_transform() * unscaled_transform; drag_type = DRAG_SCALE_BOTH; @@ -1855,7 +1843,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { drag_from = transform.affine_inverse().xform(b->get_position()); drag_selection = List<CanvasItem *>(); - drag_selection.push_back(canvas_item); + drag_selection.push_back(ci); _save_canvas_item_state(drag_selection); return true; } @@ -1867,22 +1855,22 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { // Resize the node if (m.is_valid()) { _restore_canvas_item_state(drag_selection); - CanvasItem *canvas_item = drag_selection[0]; + CanvasItem *ci = drag_selection[0]; drag_to = transform.affine_inverse().xform(m->get_position()); - Transform2D parent_xform = canvas_item->get_global_transform_with_canvas() * canvas_item->get_transform().affine_inverse(); - Transform2D unscaled_transform = (transform * parent_xform * canvas_item->_edit_get_transform()).orthonormalized(); + Transform2D parent_xform = ci->get_global_transform_with_canvas() * ci->get_transform().affine_inverse(); + Transform2D unscaled_transform = (transform * parent_xform * ci->_edit_get_transform()).orthonormalized(); Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform; bool uniform = m->is_shift_pressed(); - bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); + bool is_ctrl = m->is_ctrl_pressed(); Point2 drag_from_local = simple_xform.xform(drag_from); Point2 drag_to_local = simple_xform.xform(drag_to); Point2 offset = drag_to_local - drag_from_local; - Size2 scale = canvas_item->call("get_scale"); + Size2 scale = ci->_edit_get_scale(); Size2 original_scale = scale; real_t ratio = scale.y / scale.x; if (drag_type == DRAG_SCALE_BOTH) { @@ -1920,7 +1908,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { } } - canvas_item->call("set_scale", scale); + ci->_edit_set_scale(scale); return true; } @@ -1944,16 +1932,16 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { _insert_animation_keys(false, false, true, true); } - drag_type = DRAG_NONE; - viewport->update(); + _reset_drag(); + viewport->queue_redraw(); return true; } // Cancel a drag if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) { _restore_canvas_item_state(drag_selection); - drag_type = DRAG_NONE; - viewport->update(); + _reset_drag(); + viewport->queue_redraw(); return true; } } @@ -1968,7 +1956,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { if (drag_type == DRAG_NONE) { //Start moving the nodes if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) { - if ((b->is_alt_pressed() && !b->is_ctrl_pressed()) || tool == TOOL_MOVE) { + if ((b->is_alt_pressed() && !b->is_command_or_control_pressed()) || tool == TOOL_MOVE) { List<CanvasItem *> selection = _get_edited_canvas_items(); drag_selection.clear(); @@ -1981,9 +1969,9 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { if (selection.size() > 0) { drag_type = DRAG_MOVE; - CanvasItem *canvas_item = selection[0]; - Transform2D parent_xform = canvas_item->get_global_transform_with_canvas() * canvas_item->get_transform().affine_inverse(); - Transform2D unscaled_transform = (transform * parent_xform * canvas_item->_edit_get_transform()).orthonormalized(); + CanvasItem *ci = selection[0]; + Transform2D parent_xform = ci->get_global_transform_with_canvas() * ci->get_transform().affine_inverse(); + Transform2D unscaled_transform = (transform * parent_xform * ci->_edit_get_transform()).orthonormalized(); Transform2D simple_xform = viewport->get_transform() * unscaled_transform; if (show_transformation_gizmos) { @@ -2039,12 +2027,9 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { } } - int index = 0; - for (CanvasItem *canvas_item : drag_selection) { - Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform(); - - canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos)); - index++; + for (CanvasItem *ci : drag_selection) { + Transform2D xform = ci->get_global_transform_with_canvas().affine_inverse() * ci->get_transform(); + ci->_edit_set_position(ci->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos)); } return true; } @@ -2077,8 +2062,8 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; - drag_type = DRAG_NONE; - viewport->update(); + _reset_drag(); + viewport->queue_redraw(); return true; } @@ -2087,8 +2072,8 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { _restore_canvas_item_state(drag_selection, true); snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; - drag_type = DRAG_NONE; - viewport->update(); + _reset_drag(); + viewport->queue_redraw(); return true; } } @@ -2097,8 +2082,16 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { if (k.is_valid() && k->is_pressed() && (tool == TOOL_SELECT || tool == TOOL_MOVE) && (k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::LEFT || k->get_keycode() == Key::RIGHT)) { if (!k->is_echo()) { - // Start moving the canvas items with the keyboard - drag_selection = _get_edited_canvas_items(); + // Start moving the canvas items with the keyboard, if they are movable + List<CanvasItem *> selection = _get_edited_canvas_items(); + + drag_selection.clear(); + for (CanvasItem *item : selection) { + if (_is_node_movable(item, true)) { + drag_selection.push_back(item); + } + } + drag_type = DRAG_KEY_MOVE; drag_from = Vector2(); drag_to = Vector2(); @@ -2154,12 +2147,9 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { new_pos = previous_pos + (drag_to - drag_from); } - int index = 0; - for (CanvasItem *canvas_item : drag_selection) { - Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform(); - - canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos)); - index++; + for (CanvasItem *ci : drag_selection) { + Transform2D xform = ci->get_global_transform_with_canvas().affine_inverse() * ci->get_transform(); + ci->_edit_set_position(ci->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos)); } } return true; @@ -2186,9 +2176,9 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { drag_selection[0]->_edit_get_position().y), true); } - drag_type = DRAG_NONE; + _reset_drag(); } - viewport->update(); + viewport->queue_redraw(); return true; } @@ -2201,7 +2191,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; if (drag_type == DRAG_NONE) { - if (b.is_valid() && + if (b.is_valid() && b->is_pressed() && ((b->get_button_index() == MouseButton::RIGHT && b->is_alt_pressed() && tool == TOOL_SELECT) || (b->get_button_index() == MouseButton::LEFT && tool == TOOL_LIST_SELECT))) { // Popup the selection menu list @@ -2233,19 +2223,19 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { if (_is_node_locked(item)) { locked = 1; } else { - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); Node *node = item; while (node && node != scene->get_parent()) { - CanvasItem *canvas_item_tmp = Object::cast_to<CanvasItem>(node); - if (canvas_item_tmp && node->has_meta("_edit_group_")) { + CanvasItem *ci_tmp = Object::cast_to<CanvasItem>(node); + if (ci_tmp && node->has_meta("_edit_group_")) { locked = 2; } node = node->get_parent(); } } - String suffix = String(); + String suffix; if (locked == 1) { suffix = " (" + TTR("Locked") + ")"; } else if (locked == 2) { @@ -2258,7 +2248,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { } selection_menu_additive_selection = b->is_shift_pressed(); - selection_menu->set_position(get_screen_position() + b->get_position()); + selection_menu->set_position(viewport->get_screen_transform().xform(b->get_position())); selection_menu->reset_size(); selection_menu->popup(); return true; @@ -2266,39 +2256,54 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { } if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) { + add_node_menu->clear(); + add_node_menu->add_icon_item(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")), TTR("Add Node Here"), ADD_NODE); + add_node_menu->add_icon_item(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), TTR("Instantiate Scene Here"), ADD_INSTANCE); + for (Node *node : SceneTreeDock::get_singleton()->get_node_clipboard()) { + if (Object::cast_to<CanvasItem>(node)) { + add_node_menu->add_icon_item(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")), TTR("Paste Node(s) Here"), ADD_PASTE); + break; + } + } + for (Node *node : EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list()) { + if (Object::cast_to<CanvasItem>(node)) { + add_node_menu->add_icon_item(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons")), TTR("Move Node(s) Here"), ADD_MOVE); + break; + } + } + add_node_menu->reset_size(); - add_node_menu->set_position(get_screen_transform().xform(get_local_mouse_position())); + add_node_menu->set_position(viewport->get_screen_transform().xform(b->get_position())); add_node_menu->popup(); - node_create_position = transform.affine_inverse().xform((get_local_mouse_position())); + node_create_position = transform.affine_inverse().xform(b->get_position()); return true; } - if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT && !panner->is_panning()) { // Single item selection Point2 click = transform.affine_inverse().xform(b->get_position()); - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); if (!scene) { return true; } // Find the item to select - CanvasItem *canvas_item = nullptr; + CanvasItem *ci = nullptr; Vector<_SelectResult> selection = Vector<_SelectResult>(); // Retrieve the canvas items - selection = Vector<_SelectResult>(); _get_canvas_items_at_pos(click, selection); if (!selection.is_empty()) { - canvas_item = selection[0].item; + ci = selection[0].item; } - if (!canvas_item) { + if (!ci) { // Start a box selection if (!b->is_shift_pressed()) { // Clear the selection if not additive editor_selection->clear(); - viewport->update(); + viewport->queue_redraw(); selected_from_canvas = true; }; @@ -2307,7 +2312,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { box_selecting_to = drag_from; return true; } else { - bool still_selected = _select_click_on_item(canvas_item, click, b->is_shift_pressed()); + bool still_selected = _select_click_on_item(ci, click, b->is_shift_pressed()); // Start dragging if (still_selected) { // Drag the node(s) if requested @@ -2322,7 +2327,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { if (drag_type == DRAG_QUEUED) { if (b.is_valid() && !b->is_pressed()) { - drag_type = DRAG_NONE; + _reset_drag(); return true; } if (m.is_valid()) { @@ -2351,7 +2356,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { if (drag_type == DRAG_BOX_SELECTION) { if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT) { // Confirms box selection - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); if (scene) { List<CanvasItem *> selitems; @@ -2366,29 +2371,29 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { _find_canvas_items_in_rect(Rect2(bsfrom, bsto - bsfrom), scene, &selitems); if (selitems.size() == 1 && editor_selection->get_selected_node_list().is_empty()) { - editor->push_item(selitems[0]); + EditorNode::get_singleton()->push_item(selitems[0]); } for (CanvasItem *E : selitems) { editor_selection->add_node(E); } } - drag_type = DRAG_NONE; - viewport->update(); + _reset_drag(); + viewport->queue_redraw(); return true; } if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) { // Cancel box selection - drag_type = DRAG_NONE; - viewport->update(); + _reset_drag(); + viewport->queue_redraw(); return true; } if (m.is_valid()) { // Update box selection box_selecting_to = transform.affine_inverse().xform(m->get_position()); - viewport->update(); + viewport->queue_redraw(); return true; } } @@ -2396,7 +2401,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { if (k.is_valid() && k->is_pressed() && k->get_keycode() == Key::ESCAPE && drag_type == DRAG_NONE && tool == TOOL_SELECT) { // Unselect everything editor_selection->clear(); - viewport->update(); + viewport->queue_redraw(); } return false; } @@ -2422,12 +2427,12 @@ bool CanvasItemEditor::_gui_input_ruler_tool(const Ref<InputEvent> &p_event) { ruler_tool_active = false; } - viewport->update(); + viewport->queue_redraw(); return true; } if (m.is_valid() && (ruler_tool_active || (grid_snap_active && previous_origin != ruler_tool_origin))) { - viewport->update(); + viewport->queue_redraw(); return true; } @@ -2439,7 +2444,7 @@ bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) { if (m.is_valid()) { Point2 click = transform.affine_inverse().xform(m->get_position()); - // Checks if the hovered items changed, update the viewport if so + // Checks if the hovered items changed, redraw the viewport if so Vector<_SelectResult> hovering_results_items; _get_canvas_items_at_pos(click, hovering_results_items); hovering_results_items.sort(); @@ -2447,21 +2452,21 @@ bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) { // Compute the nodes names and icon position Vector<_HoverResult> hovering_results_tmp; for (int i = 0; i < hovering_results_items.size(); i++) { - CanvasItem *canvas_item = hovering_results_items[i].item; + CanvasItem *ci = hovering_results_items[i].item; - if (canvas_item->_edit_use_rect()) { + if (ci->_edit_use_rect()) { continue; } _HoverResult hover_result; - hover_result.position = canvas_item->get_global_transform_with_canvas().get_origin(); - hover_result.icon = EditorNode::get_singleton()->get_object_icon(canvas_item); - hover_result.name = canvas_item->get_name(); + hover_result.position = ci->get_global_transform_with_canvas().get_origin(); + hover_result.icon = EditorNode::get_singleton()->get_object_icon(ci); + hover_result.name = ci->get_name(); hovering_results_tmp.push_back(hover_result); } - // Check if changed, if so, update. + // Check if changed, if so, redraw. bool changed = false; if (hovering_results_tmp.size() == hovering_results.size()) { for (int i = 0; i < hovering_results_tmp.size(); i++) { @@ -2478,7 +2483,7 @@ bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) { if (changed) { hovering_results = hovering_results_tmp; - viewport->update(); + viewport->queue_redraw(); } return true; @@ -2490,31 +2495,36 @@ bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) { void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) { bool accepted = false; - if (EditorSettings::get_singleton()->get("editors/2d/simple_panning") || !pan_pressed) { - if ((accepted = _gui_input_rulers_and_guides(p_event))) { - //printf("Rulers and guides\n"); - } else if ((accepted = editor->get_editor_plugins_over()->forward_gui_input(p_event))) { - //printf("Plugin\n"); - } else if ((accepted = _gui_input_open_scene_on_double_click(p_event))) { - //printf("Open scene on double click\n"); - } else if ((accepted = _gui_input_scale(p_event))) { - //printf("Set scale\n"); - } else if ((accepted = _gui_input_pivot(p_event))) { - //printf("Set pivot\n"); - } else if ((accepted = _gui_input_resize(p_event))) { - //printf("Resize\n"); - } else if ((accepted = _gui_input_rotate(p_event))) { - //printf("Rotate\n"); - } else if ((accepted = _gui_input_move(p_event))) { - //printf("Move\n"); - } else if ((accepted = _gui_input_anchors(p_event))) { - //printf("Anchors\n"); - } else if ((accepted = _gui_input_select(p_event))) { - //printf("Selection\n"); - } else if ((accepted = _gui_input_ruler_tool(p_event))) { - //printf("Measure\n"); + Ref<InputEventMouseButton> mb = p_event; + bool release_lmb = (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT); // Required to properly release some stuff (e.g. selection box) while panning. + + if (EDITOR_GET("editors/panning/simple_panning") || !pan_pressed || release_lmb) { + accepted = true; + if (_gui_input_rulers_and_guides(p_event)) { + // print_line("Rulers and guides"); + } else if (EditorNode::get_singleton()->get_editor_plugins_over()->forward_gui_input(p_event)) { + // print_line("Plugin"); + } else if (_gui_input_open_scene_on_double_click(p_event)) { + // print_line("Open scene on double click"); + } else if (_gui_input_scale(p_event)) { + // print_line("Set scale"); + } else if (_gui_input_pivot(p_event)) { + // print_line("Set pivot"); + } else if (_gui_input_resize(p_event)) { + // print_line("Resize"); + } else if (_gui_input_rotate(p_event)) { + // print_line("Rotate"); + } else if (_gui_input_move(p_event)) { + // print_line("Move"); + } else if (_gui_input_anchors(p_event)) { + // print_line("Anchors"); + } else if (_gui_input_select(p_event)) { + // print_line("Selection"); + } else if (_gui_input_ruler_tool(p_event)) { + // print_line("Measure"); } else { - //printf("Not accepted\n"); + // print_line("Not accepted"); + accepted = false; } } @@ -2527,18 +2537,45 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) { // Handles the mouse hovering _gui_input_hover(p_event); - // Change the cursor - _update_cursor(); + if (mb.is_valid()) { + // Update the default cursor. + _update_cursor(); + } // Grab focus - if (!viewport->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field())) { + if (!viewport->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) { viewport->call_deferred(SNAME("grab_focus")); } } void CanvasItemEditor::_update_cursor() { + // Choose the correct default cursor. + CursorShape c = CURSOR_ARROW; + switch (tool) { + case TOOL_MOVE: + c = CURSOR_MOVE; + break; + case TOOL_EDIT_PIVOT: + c = CURSOR_CROSS; + break; + case TOOL_PAN: + c = CURSOR_DRAG; + break; + case TOOL_RULER: + c = CURSOR_CROSS; + break; + default: + break; + } + if (pan_pressed) { + c = CURSOR_DRAG; + } + set_default_cursor_shape(c); +} + +Control::CursorShape CanvasItemEditor::get_cursor_shape(const Point2 &p_pos) const { // Compute an eventual rotation of the cursor - CursorShape rotation_array[4] = { CURSOR_HSIZE, CURSOR_BDIAGSIZE, CURSOR_VSIZE, CURSOR_FDIAGSIZE }; + const CursorShape rotation_array[4] = { CURSOR_HSIZE, CURSOR_BDIAGSIZE, CURSOR_VSIZE, CURSOR_FDIAGSIZE }; int rotation_array_index = 0; List<CanvasItem *> selection = _get_edited_canvas_items(); @@ -2558,26 +2595,8 @@ void CanvasItemEditor::_update_cursor() { } // Choose the correct cursor - CursorShape c = CURSOR_ARROW; + CursorShape c = get_default_cursor_shape(); switch (drag_type) { - case DRAG_NONE: - switch (tool) { - case TOOL_MOVE: - c = CURSOR_MOVE; - break; - case TOOL_EDIT_PIVOT: - c = CURSOR_CROSS; - break; - case TOOL_PAN: - c = CURSOR_DRAG; - break; - case TOOL_RULER: - c = CURSOR_CROSS; - break; - default: - break; - } - break; case DRAG_LEFT: case DRAG_RIGHT: c = rotation_array[rotation_array_index]; @@ -2619,16 +2638,7 @@ void CanvasItemEditor::_update_cursor() { if (pan_pressed) { c = CURSOR_DRAG; } - - if (c != viewport->get_default_cursor_shape()) { - viewport->set_default_cursor_shape(c); - - // Force refresh cursor if it's over the viewport. - if (viewport->get_global_rect().has_point(get_global_mouse_position())) { - DisplayServer::CursorShape ds_cursor_shape = (DisplayServer::CursorShape)viewport->get_default_cursor_shape(); - DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape); - } - } + return c; } void CanvasItemEditor::_draw_text_at_position(Point2 p_position, String p_string, Side p_side) { @@ -2636,7 +2646,7 @@ void CanvasItemEditor::_draw_text_at_position(Point2 p_position, String p_string color.a = 0.8; Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); - Size2 text_size = font->get_string_size(p_string, font_size); + Size2 text_size = font->get_string_size(p_string, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); switch (p_side) { case SIDE_LEFT: p_position += Vector2(-text_size.x - 5, text_size.y / 2); @@ -2676,12 +2686,12 @@ void CanvasItemEditor::_draw_focus() { } void CanvasItemEditor::_draw_guides() { - Color guide_color = EditorSettings::get_singleton()->get("editors/2d/guides_color"); + Color guide_color = EDITOR_GET("editors/2d/guides_color"); Transform2D xform = viewport_scrollable->get_transform() * transform; - // Guides already there - if (EditorNode::get_singleton()->get_edited_scene() && EditorNode::get_singleton()->get_edited_scene()->has_meta("_edit_vertical_guides_")) { - Array vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_"); + // Guides already there. + if (Node *scene = EditorNode::get_singleton()->get_edited_scene()) { + Array vguides = scene->get_meta("_edit_vertical_guides_", Array()); for (int i = 0; i < vguides.size(); i++) { if (drag_type == DRAG_V_GUIDE && i == dragged_guide_index) { continue; @@ -2689,10 +2699,8 @@ void CanvasItemEditor::_draw_guides() { real_t x = xform.xform(Point2(vguides[i], 0)).x; viewport->draw_line(Point2(x, 0), Point2(x, viewport->get_size().y), guide_color, Math::round(EDSCALE)); } - } - if (EditorNode::get_singleton()->get_edited_scene() && EditorNode::get_singleton()->get_edited_scene()->has_meta("_edit_horizontal_guides_")) { - Array hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_"); + Array hguides = scene->get_meta("_edit_horizontal_guides_", Array()); for (int i = 0; i < hguides.size(); i++) { if (drag_type == DRAG_H_GUIDE && i == dragged_guide_index) { continue; @@ -2702,30 +2710,32 @@ void CanvasItemEditor::_draw_guides() { } } - // Dragged guide + // Dragged guide. Color text_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); Color outline_color = text_color.inverted(); const float outline_size = 2; if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_V_GUIDE) { String str = TS->format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).x))); Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); - int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); - Size2 text_size = font->get_string_size(str, font_size); - viewport->draw_string(font, Point2(dragged_guide_pos.x + 10, RULER_WIDTH + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color, outline_size, outline_color); + int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); + Size2 text_size = font->get_string_size(str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); + viewport->draw_string_outline(font, Point2(dragged_guide_pos.x + 10, RULER_WIDTH + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, Point2(dragged_guide_pos.x + 10, RULER_WIDTH + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); viewport->draw_line(Point2(dragged_guide_pos.x, 0), Point2(dragged_guide_pos.x, viewport->get_size().y), guide_color, Math::round(EDSCALE)); } if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_H_GUIDE) { String str = TS->format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).y))); Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); - int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); - Size2 text_size = font->get_string_size(str, font_size); - viewport->draw_string(font, Point2(RULER_WIDTH + 10, dragged_guide_pos.y + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color, outline_size, outline_color); + int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); + Size2 text_size = font->get_string_size(str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); + viewport->draw_string_outline(font, Point2(RULER_WIDTH + 10, dragged_guide_pos.y + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, Point2(RULER_WIDTH + 10, dragged_guide_pos.y + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); viewport->draw_line(Point2(0, dragged_guide_pos.y), Point2(viewport->get_size().x, dragged_guide_pos.y), guide_color, Math::round(EDSCALE)); } } void CanvasItemEditor::_draw_smart_snapping() { - Color line_color = EditorSettings::get_singleton()->get("editors/2d/smart_snapping_line_color"); + Color line_color = EDITOR_GET("editors/2d/smart_snapping_line_color"); if (snap_target[0] != SNAP_TARGET_NONE && snap_target[0] != SNAP_TARGET_GRID) { viewport->draw_set_transform_matrix(viewport->get_transform() * transform * snap_transform); viewport->draw_line(Point2(0, -1.0e+10F), Point2(0, 1.0e+10F), line_color); @@ -2747,14 +2757,14 @@ void CanvasItemEditor::_draw_rulers() { int font_size = get_theme_font_size(SNAME("rulers_size"), SNAME("EditorFonts")); // The rule transform - Transform2D ruler_transform = Transform2D(); - if (show_grid || grid_snap_active) { + Transform2D ruler_transform; + if (grid_snap_active || _is_grid_visible()) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (snap_relative && selection.size() > 0) { - ruler_transform.translate(_get_encompassing_rect_from_list(selection).position); + ruler_transform.translate_local(_get_encompassing_rect_from_list(selection).position); ruler_transform.scale_basis(grid_step * Math::pow(2.0, grid_step_multiplier)); } else { - ruler_transform.translate(grid_offset); + ruler_transform.translate_local(grid_offset); ruler_transform.scale_basis(grid_step * Math::pow(2.0, grid_step_multiplier)); } while ((transform * ruler_transform).get_scale().x < 50 || (transform * ruler_transform).get_scale().y < 50) { @@ -2773,11 +2783,11 @@ void CanvasItemEditor::_draw_rulers() { // Subdivisions int major_subdivision = 2; - Transform2D major_subdivide = Transform2D(); + Transform2D major_subdivide; major_subdivide.scale(Size2(1.0 / major_subdivision, 1.0 / major_subdivision)); int minor_subdivision = 5; - Transform2D minor_subdivide = Transform2D(); + Transform2D minor_subdivide; minor_subdivide.scale(Size2(1.0 / minor_subdivision, 1.0 / minor_subdivision)); // First and last graduations to draw (in the ruler space) @@ -2828,7 +2838,7 @@ void CanvasItemEditor::_draw_rulers() { } void CanvasItemEditor::_draw_grid() { - if (show_grid || grid_snap_active) { + if (_is_grid_visible()) { // Draw the grid Vector2 real_grid_offset; const List<CanvasItem *> selection = _get_edited_canvas_items(); @@ -2843,7 +2853,7 @@ void CanvasItemEditor::_draw_grid() { // Draw a "primary" line every several lines to make measurements easier. // The step is configurable in the Configure Snap dialog. - const Color secondary_grid_color = EditorSettings::get_singleton()->get("editors/2d/grid_color"); + const Color secondary_grid_color = EDITOR_GET("editors/2d/grid_color"); const Color primary_grid_color = Color(secondary_grid_color.r, secondary_grid_color.g, secondary_grid_color.b, secondary_grid_color.a * 2.5); @@ -2914,15 +2924,18 @@ void CanvasItemEditor::_draw_ruler_tool() { Point2 corner = Point2(begin.x, end.y); Vector2 length_vector = (begin - end).abs() / zoom; + const real_t horizontal_angle_rad = length_vector.angle(); + const real_t vertical_angle_rad = Math_PI / 2.0 - horizontal_angle_rad; + Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); - int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); + int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); Color font_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); Color font_secondary_color = font_color; font_secondary_color.set_v(font_secondary_color.get_v() > 0.5 ? 0.7 : 0.3); Color outline_color = font_color.inverted(); float text_height = font->get_height(font_size); - const float outline_size = 2; + const float outline_size = 4; const float text_width = 76; const float angle_text_width = 54; @@ -2930,43 +2943,74 @@ void CanvasItemEditor::_draw_ruler_tool() { text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5); text_pos.y = CLAMP(text_pos.y, text_height * 1.5, viewport->get_rect().size.y - text_height * 1.5); - if (begin.is_equal_approx(end)) { - viewport->draw_string(font, text_pos, (String)ruler_tool_origin, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color, outline_size, outline_color); - Ref<Texture2D> position_icon = get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")); - viewport->draw_texture(get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")), (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2); - return; - } - - viewport->draw_string(font, text_pos, TS->format_number(vformat("%.1f px", length_vector.length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color, outline_size, outline_color); + // Draw lines. + viewport->draw_line(begin, end, ruler_primary_color, Math::round(EDSCALE * 3)); bool draw_secondary_lines = !(Math::is_equal_approx(begin.y, corner.y) || Math::is_equal_approx(end.x, corner.x)); - - viewport->draw_line(begin, end, ruler_primary_color, Math::round(EDSCALE * 3)); if (draw_secondary_lines) { viewport->draw_line(begin, corner, ruler_secondary_color, Math::round(EDSCALE)); viewport->draw_line(corner, end, ruler_secondary_color, Math::round(EDSCALE)); + + // Angle arcs. + int arc_point_count = 8; + real_t arc_radius_max_length_percent = 0.1; + real_t ruler_length = length_vector.length() * zoom; + real_t arc_max_radius = 50.0; + real_t arc_line_width = 2.0; + + const Vector2 end_to_begin = (end - begin); + + real_t arc_1_start_angle = end_to_begin.x < 0 + ? (end_to_begin.y < 0 ? 3.0 * Math_PI / 2.0 - vertical_angle_rad : Math_PI / 2.0) + : (end_to_begin.y < 0 ? 3.0 * Math_PI / 2.0 : Math_PI / 2.0 - vertical_angle_rad); + real_t arc_1_end_angle = arc_1_start_angle + vertical_angle_rad; + // Constrain arc to triangle height & max size. + real_t arc_1_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, ABS(end_to_begin.y)), arc_max_radius); + + real_t arc_2_start_angle = end_to_begin.x < 0 + ? (end_to_begin.y < 0 ? 0.0 : -horizontal_angle_rad) + : (end_to_begin.y < 0 ? Math_PI - horizontal_angle_rad : Math_PI); + real_t arc_2_end_angle = arc_2_start_angle + horizontal_angle_rad; + // Constrain arc to triangle width & max size. + real_t arc_2_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, ABS(end_to_begin.x)), arc_max_radius); + + viewport->draw_arc(begin, arc_1_radius, arc_1_start_angle, arc_1_end_angle, arc_point_count, ruler_primary_color, Math::round(EDSCALE * arc_line_width)); + viewport->draw_arc(end, arc_2_radius, arc_2_start_angle, arc_2_end_angle, arc_point_count, ruler_primary_color, Math::round(EDSCALE * arc_line_width)); + } + + // Draw text. + if (begin.is_equal_approx(end)) { + viewport->draw_string_outline(font, text_pos, (String)ruler_tool_origin, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos, (String)ruler_tool_origin, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); + Ref<Texture2D> position_icon = get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")); + viewport->draw_texture(get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")), (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2); + return; } + viewport->draw_string_outline(font, text_pos, TS->format_number(vformat("%.1f px", length_vector.length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos, TS->format_number(vformat("%.1f px", length_vector.length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); + if (draw_secondary_lines) { - const real_t horizontal_angle_rad = length_vector.angle(); - const real_t vertical_angle_rad = Math_PI / 2.0 - horizontal_angle_rad; const int horizontal_angle = round(180 * horizontal_angle_rad / Math_PI); const int vertical_angle = round(180 * vertical_angle_rad / Math_PI); Point2 text_pos2 = text_pos; text_pos2.x = begin.x < text_pos.x ? MIN(text_pos.x - text_width, begin.x - text_width / 2) : MAX(text_pos.x + text_width, begin.x - text_width / 2); - viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); + viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); - Point2 v_angle_text_pos = Point2(); + Point2 v_angle_text_pos; v_angle_text_pos.x = CLAMP(begin.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width); v_angle_text_pos.y = begin.y < end.y ? MIN(text_pos2.y - 2 * text_height, begin.y - text_height * 0.5) : MAX(text_pos2.y + text_height * 3, begin.y + text_height * 1.5); - viewport->draw_string(font, v_angle_text_pos, TS->format_number(vformat(String::utf8("%d°"), vertical_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); + viewport->draw_string_outline(font, v_angle_text_pos, TS->format_number(vformat(String::utf8("%d°"), vertical_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, v_angle_text_pos, TS->format_number(vformat(String::utf8("%d°"), vertical_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); text_pos2 = text_pos; text_pos2.y = end.y < text_pos.y ? MIN(text_pos.y - text_height * 2, end.y - text_height / 2) : MAX(text_pos.y + text_height * 2, end.y - text_height / 2); - viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); + viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); - Point2 h_angle_text_pos = Point2(); + Point2 h_angle_text_pos; h_angle_text_pos.x = CLAMP(end.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width); if (begin.y < end.y) { h_angle_text_pos.y = end.y + text_height * 1.5; @@ -2981,33 +3025,8 @@ void CanvasItemEditor::_draw_ruler_tool() { h_angle_text_pos.y = MIN(text_pos.y - height_multiplier * text_height, MIN(end.y - text_height * 0.5, text_pos2.y - height_multiplier * text_height)); } } - viewport->draw_string(font, h_angle_text_pos, TS->format_number(vformat(String::utf8("%d°"), horizontal_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); - - // Angle arcs - int arc_point_count = 8; - real_t arc_radius_max_length_percent = 0.1; - real_t ruler_length = length_vector.length() * zoom; - real_t arc_max_radius = 50.0; - real_t arc_line_width = 2.0; - - const Vector2 end_to_begin = (end - begin); - - real_t arc_1_start_angle = end_to_begin.x < 0 - ? (end_to_begin.y < 0 ? 3.0 * Math_PI / 2.0 - vertical_angle_rad : Math_PI / 2.0) - : (end_to_begin.y < 0 ? 3.0 * Math_PI / 2.0 : Math_PI / 2.0 - vertical_angle_rad); - real_t arc_1_end_angle = arc_1_start_angle + vertical_angle_rad; - // Constrain arc to triangle height & max size - real_t arc_1_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, ABS(end_to_begin.y)), arc_max_radius); - - real_t arc_2_start_angle = end_to_begin.x < 0 - ? (end_to_begin.y < 0 ? 0.0 : -horizontal_angle_rad) - : (end_to_begin.y < 0 ? Math_PI - horizontal_angle_rad : Math_PI); - real_t arc_2_end_angle = arc_2_start_angle + horizontal_angle_rad; - // Constrain arc to triangle width & max size - real_t arc_2_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, ABS(end_to_begin.x)), arc_max_radius); - - viewport->draw_arc(begin, arc_1_radius, arc_1_start_angle, arc_1_end_angle, arc_point_count, ruler_primary_color, Math::round(EDSCALE * arc_line_width)); - viewport->draw_arc(end, arc_2_radius, arc_2_start_angle, arc_2_end_angle, arc_point_count, ruler_primary_color, Math::round(EDSCALE * arc_line_width)); + viewport->draw_string_outline(font, h_angle_text_pos, TS->format_number(vformat(String::utf8("%d°"), horizontal_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, h_angle_text_pos, TS->format_number(vformat(String::utf8("%d°"), horizontal_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); } if (grid_snap_active) { @@ -3016,17 +3035,21 @@ void CanvasItemEditor::_draw_ruler_tool() { text_pos.y = CLAMP(text_pos.y, text_height * 2.5, viewport->get_rect().size.y - text_height / 2); if (draw_secondary_lines) { - viewport->draw_string(font, text_pos, TS->format_number(vformat("%.2f " + TTR("units"), (length_vector / grid_step).length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color, outline_size, outline_color); + viewport->draw_string_outline(font, text_pos, TS->format_number(vformat("%.2f " + TTR("units"), (length_vector / grid_step).length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos, TS->format_number(vformat("%.2f " + TTR("units"), (length_vector / grid_step).length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); Point2 text_pos2 = text_pos; text_pos2.x = begin.x < text_pos.x ? MIN(text_pos.x - text_width, begin.x - text_width / 2) : MAX(text_pos.x + text_width, begin.x - text_width / 2); - viewport->draw_string(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.y / grid_step.y))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); + viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.y / grid_step.y))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.y / grid_step.y))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); text_pos2 = text_pos; text_pos2.y = end.y < text_pos.y ? MIN(text_pos.y - text_height * 2, end.y + text_height / 2) : MAX(text_pos.y + text_height * 2, end.y + text_height / 2); - viewport->draw_string(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.x / grid_step.x))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); + viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.x / grid_step.x))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.x / grid_step.x))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); } else { - viewport->draw_string(font, text_pos, TS->format_number(vformat("%d " + TTR("units"), roundf((length_vector / grid_step).length()))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color, outline_size, outline_color); + viewport->draw_string_outline(font, text_pos, TS->format_number(vformat("%d " + TTR("units"), roundf((length_vector / grid_step).length()))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos, TS->format_number(vformat("%d " + TTR("units"), roundf((length_vector / grid_step).length()))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); } } } else { @@ -3116,7 +3139,6 @@ void CanvasItemEditor::_draw_control_helpers(Control *control) { if (dragged_anchor >= 0) { // Draw the 4 lines when dragged - bool anchor_snapped; Color color_snapped = Color(0.64, 0.93, 0.67, 0.5); Vector2 corners_pos[4]; @@ -3130,7 +3152,7 @@ void CanvasItemEditor::_draw_control_helpers(Control *control) { real_t anchor_val = (i >= 2) ? ANCHOR_END - anchors_values[i] : anchors_values[i]; line_starts[i] = corners_pos[i].lerp(corners_pos[(i + 1) % 4], anchor_val); line_ends[i] = corners_pos[(i + 3) % 4].lerp(corners_pos[(i + 2) % 4], anchor_val); - anchor_snapped = anchors_values[i] == 0.0 || anchors_values[i] == 0.5 || anchors_values[i] == 1.0; + bool anchor_snapped = anchors_values[i] == 0.0 || anchors_values[i] == 0.5 || anchors_values[i] == 1.0; viewport->draw_line(line_starts[i], line_ends[i], anchor_snapped ? color_snapped : color_base, (i == dragged_anchor || (i + 3) % 4 == dragged_anchor) ? 2 : 1); } @@ -3254,16 +3276,16 @@ void CanvasItemEditor::_draw_selection() { Ref<Texture2D> position_icon = get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")); Ref<Texture2D> previous_position_icon = get_theme_icon(SNAME("EditorPositionPrevious"), SNAME("EditorIcons")); - RID ci = viewport->get_canvas_item(); + RID vp_ci = viewport->get_canvas_item(); List<CanvasItem *> selection = _get_edited_canvas_items(true, false); bool single = selection.size() == 1; for (CanvasItem *E : selection) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E); - CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); + CanvasItem *ci = Object::cast_to<CanvasItem>(E); + CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci); - bool item_locked = canvas_item->has_meta("_edit_lock_"); + bool item_locked = ci->has_meta("_edit_lock_"); // Draw the previous position if we are dragging the node if (show_helpers && @@ -3273,7 +3295,7 @@ void CanvasItemEditor::_draw_selection() { const Transform2D pre_drag_xform = transform * se->pre_drag_xform; const Color pre_drag_color = Color(0.4, 0.6, 1, 0.7); - if (canvas_item->_edit_use_rect()) { + if (ci->_edit_use_rect()) { Vector2 pre_drag_endpoints[4] = { pre_drag_xform.xform(se->pre_drag_rect.position), pre_drag_xform.xform(se->pre_drag_rect.position + Vector2(se->pre_drag_rect.size.x, 0)), @@ -3289,12 +3311,12 @@ void CanvasItemEditor::_draw_selection() { } } - Transform2D xform = transform * canvas_item->get_global_transform_with_canvas(); + Transform2D xform = transform * ci->get_global_transform_with_canvas(); // Draw the selected items position / surrounding boxes - if (canvas_item->_edit_use_rect()) { - Rect2 rect = canvas_item->_edit_get_rect(); - Vector2 endpoints[4] = { + if (ci->_edit_use_rect()) { + Rect2 rect = ci->_edit_get_rect(); + const Vector2 endpoints[4] = { xform.xform(rect.position), xform.xform(rect.position + Vector2(rect.size.x, 0)), xform.xform(rect.position + rect.size), @@ -3311,7 +3333,7 @@ void CanvasItemEditor::_draw_selection() { viewport->draw_line(endpoints[i], endpoints[(i + 1) % 4], c, Math::round(2 * EDSCALE)); } } else { - Transform2D unscaled_transform = (xform * canvas_item->get_transform().affine_inverse() * canvas_item->_edit_get_transform()).orthonormalized(); + Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized(); Transform2D simple_xform = viewport->get_transform() * unscaled_transform; viewport->draw_set_transform_matrix(simple_xform); viewport->draw_texture(position_icon, -(position_icon->get_size() / 2)); @@ -3320,9 +3342,9 @@ void CanvasItemEditor::_draw_selection() { if (single && !item_locked && (tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE || tool == TOOL_EDIT_PIVOT)) { //kind of sucks // Draw the pivot - if (canvas_item->_edit_use_pivot()) { + if (ci->_edit_use_pivot()) { // Draw the node's pivot - Transform2D unscaled_transform = (xform * canvas_item->get_transform().affine_inverse() * canvas_item->_edit_get_transform()).orthonormalized(); + Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized(); Transform2D simple_xform = viewport->get_transform() * unscaled_transform; viewport->draw_set_transform_matrix(simple_xform); @@ -3331,16 +3353,16 @@ void CanvasItemEditor::_draw_selection() { } // Draw control-related helpers - Control *control = Object::cast_to<Control>(canvas_item); + Control *control = Object::cast_to<Control>(ci); if (control && _is_node_movable(control)) { _draw_control_anchors(control); _draw_control_helpers(control); } // Draw the resize handles - if (tool == TOOL_SELECT && canvas_item->_edit_use_rect() && _is_node_movable(canvas_item)) { - Rect2 rect = canvas_item->_edit_get_rect(); - Vector2 endpoints[4] = { + if (tool == TOOL_SELECT && ci->_edit_use_rect() && _is_node_movable(ci)) { + Rect2 rect = ci->_edit_get_rect(); + const Vector2 endpoints[4] = { xform.xform(rect.position), xform.xform(rect.position + Vector2(rect.size.x, 0)), xform.xform(rect.position + rect.size), @@ -3353,30 +3375,31 @@ void CanvasItemEditor::_draw_selection() { Vector2 ofs = ((endpoints[i] - endpoints[prev]).normalized() + ((endpoints[i] - endpoints[next]).normalized())).normalized(); ofs *= Math_SQRT2 * (select_handle->get_size().width / 2); - select_handle->draw(ci, (endpoints[i] + ofs - (select_handle->get_size() / 2)).floor()); + select_handle->draw(vp_ci, (endpoints[i] + ofs - (select_handle->get_size() / 2)).floor()); ofs = (endpoints[i] + endpoints[next]) / 2; ofs += (endpoints[next] - endpoints[i]).orthogonal().normalized() * (select_handle->get_size().width / 2); - select_handle->draw(ci, (ofs - (select_handle->get_size() / 2)).floor()); + select_handle->draw(vp_ci, (ofs - (select_handle->get_size() / 2)).floor()); } } // Draw the move handles - bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); + bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL); bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT); if (tool == TOOL_MOVE && show_transformation_gizmos) { - if (_is_node_movable(canvas_item)) { - Transform2D unscaled_transform = (xform * canvas_item->get_transform().affine_inverse() * canvas_item->_edit_get_transform()).orthonormalized(); + if (_is_node_movable(ci)) { + Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized(); Transform2D simple_xform = viewport->get_transform() * unscaled_transform; Size2 move_factor = Size2(MOVE_HANDLE_DISTANCE, MOVE_HANDLE_DISTANCE); viewport->draw_set_transform_matrix(simple_xform); - Vector<Point2> points; - points.push_back(Vector2(move_factor.x * EDSCALE, 5 * EDSCALE)); - points.push_back(Vector2(move_factor.x * EDSCALE, -5 * EDSCALE)); - points.push_back(Vector2((move_factor.x + 10) * EDSCALE, 0)); + Vector<Point2> points = { + Vector2(move_factor.x * EDSCALE, 5 * EDSCALE), + Vector2(move_factor.x * EDSCALE, -5 * EDSCALE), + Vector2((move_factor.x + 10) * EDSCALE, 0) + }; viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); viewport->draw_line(Point2(), Point2(move_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), SNAME("Editor")), Math::round(EDSCALE)); @@ -3395,8 +3418,8 @@ void CanvasItemEditor::_draw_selection() { // Draw the rescale handles if (show_transformation_gizmos && ((is_alt && is_ctrl) || tool == TOOL_SCALE || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y)) { - if (_is_node_movable(canvas_item)) { - Transform2D unscaled_transform = (xform * canvas_item->get_transform().affine_inverse() * canvas_item->_edit_get_transform()).orthonormalized(); + if (_is_node_movable(ci)) { + Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized(); Transform2D simple_xform = viewport->get_transform() * unscaled_transform; Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE); @@ -3506,9 +3529,9 @@ void CanvasItemEditor::_draw_axis() { if (show_viewport) { RID ci = viewport->get_canvas_item(); - Color area_axis_color = EditorSettings::get_singleton()->get("editors/2d/viewport_border_color"); + Color area_axis_color = EDITOR_GET("editors/2d/viewport_border_color"); - Size2 screen_size = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height")); + Size2 screen_size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); Vector2 screen_endpoints[4] = { transform.xform(Vector2(0, 0)), @@ -3526,20 +3549,20 @@ void CanvasItemEditor::_draw_axis() { void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) { ERR_FAIL_COND(!p_node); - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node->get_owner())) { return; } - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); - if (canvas_item && !canvas_item->is_visible()) { + CanvasItem *ci = Object::cast_to<CanvasItem>(p_node); + if (ci && !ci->is_visible_in_tree()) { return; } Transform2D parent_xform = p_parent_xform; Transform2D canvas_xform = p_canvas_xform; - if (canvas_item && !canvas_item->is_set_as_top_level()) { - parent_xform = parent_xform * canvas_item->get_transform(); + if (ci && !ci->is_set_as_top_level()) { + parent_xform = parent_xform * ci->get_transform(); } else { CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); parent_xform = Transform2D(); @@ -3550,12 +3573,12 @@ void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Trans _draw_invisible_nodes_positions(p_node->get_child(i), parent_xform, canvas_xform); } - if (canvas_item && !canvas_item->_edit_use_rect() && (!editor_selection->is_selected(canvas_item) || _is_node_locked(canvas_item))) { + if (ci && !ci->_edit_use_rect() && (!editor_selection->is_selected(ci) || _is_node_locked(ci))) { Transform2D xform = transform * canvas_xform * parent_xform; // Draw the node's position Ref<Texture2D> position_icon = get_theme_icon(SNAME("EditorPositionUnselected"), SNAME("EditorIcons")); - Transform2D unscaled_transform = (xform * canvas_item->get_transform().affine_inverse() * canvas_item->_edit_get_transform()).orthonormalized(); + Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized(); Transform2D simple_xform = viewport->get_transform() * unscaled_transform; viewport->draw_set_transform_matrix(simple_xform); viewport->draw_texture(position_icon, -position_icon->get_size() / 2, Color(1.0, 1.0, 1.0, 0.5)); @@ -3572,7 +3595,7 @@ void CanvasItemEditor::_draw_hover() { Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); - Size2 node_name_size = font->get_string_size(node_name, font_size); + Size2 node_name_size = font->get_string_size(node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); Size2 item_size = Size2(node_icon->get_size().x + 4 + node_name_size.x, MAX(node_icon->get_size().y, node_name_size.y - 3)); Point2 pos = transform.xform(hovering_results[i].position) - Point2(0, item_size.y) + (Point2(node_icon->get_size().x, -node_icon->get_size().y) / 4); @@ -3593,23 +3616,84 @@ void CanvasItemEditor::_draw_hover() { } } +void CanvasItemEditor::_draw_transform_message() { + if (drag_type == DRAG_NONE || drag_selection.is_empty() || !drag_selection.front()->get()) { + return; + } + String transform_message; + Transform2D current_transform = drag_selection.front()->get()->get_global_transform(); + + double snap = EDITOR_GET("interface/inspector/default_float_step"); + int snap_step_decimals = Math::range_step_decimals(snap); +#define FORMAT(value) (TS->format_number(String::num(value, snap_step_decimals))) + + switch (drag_type) { + case DRAG_MOVE: + case DRAG_MOVE_X: + case DRAG_MOVE_Y: { + Vector2 delta = current_transform.get_origin() - original_transform.get_origin(); + if (drag_type == DRAG_MOVE) { + transform_message = TTR("Moving:") + " (" + FORMAT(delta.x) + ", " + FORMAT(delta.y) + ") px"; + } else if (drag_type == DRAG_MOVE_X) { + transform_message = TTR("Moving:") + " " + FORMAT(delta.x) + " px"; + } else if (drag_type == DRAG_MOVE_Y) { + transform_message = TTR("Moving:") + " " + FORMAT(delta.y) + " px"; + } + } break; + + case DRAG_ROTATE: { + real_t delta = Math::rad_to_deg(current_transform.get_rotation() - original_transform.get_rotation()); + transform_message = TTR("Rotating:") + " " + FORMAT(delta) + String::utf8(" °"); + } break; + + case DRAG_SCALE_X: + case DRAG_SCALE_Y: + case DRAG_SCALE_BOTH: { + Vector2 original_scale = (Math::is_zero_approx(original_transform.get_scale().x) || Math::is_zero_approx(original_transform.get_scale().y)) ? Vector2(CMP_EPSILON, CMP_EPSILON) : original_transform.get_scale(); + Vector2 delta = current_transform.get_scale() / original_scale; + if (drag_type == DRAG_SCALE_BOTH) { + transform_message = TTR("Scaling:") + String::utf8(" ×(") + FORMAT(delta.x) + ", " + FORMAT(delta.y) + ")"; + } else if (drag_type == DRAG_SCALE_X) { + transform_message = TTR("Scaling:") + String::utf8(" ×") + FORMAT(delta.x); + } else if (drag_type == DRAG_SCALE_Y) { + transform_message = TTR("Scaling:") + String::utf8(" ×") + FORMAT(delta.y); + } + } break; + + default: + break; + } +#undef FORMAT + + if (transform_message.is_empty()) { + return; + } + + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); + Point2 msgpos = Point2(RULER_WIDTH + 5 * EDSCALE, viewport->get_size().y - 20 * EDSCALE); + viewport->draw_string(font, msgpos + Point2(1, 1), transform_message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8)); + viewport->draw_string(font, msgpos + Point2(-1, -1), transform_message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8)); + viewport->draw_string(font, msgpos, transform_message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 1)); +} + void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) { ERR_FAIL_COND(!p_node); - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node->get_owner())) { return; } - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); - if (canvas_item && !canvas_item->is_visible()) { + CanvasItem *ci = Object::cast_to<CanvasItem>(p_node); + if (ci && !ci->is_visible_in_tree()) { return; } Transform2D parent_xform = p_parent_xform; Transform2D canvas_xform = p_canvas_xform; - if (canvas_item && !canvas_item->is_set_as_top_level()) { - parent_xform = parent_xform * canvas_item->get_transform(); + if (ci && !ci->is_set_as_top_level()) { + parent_xform = parent_xform * ci->get_transform(); } else { CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); parent_xform = Transform2D(); @@ -3620,19 +3704,19 @@ void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p _draw_locks_and_groups(p_node->get_child(i), parent_xform, canvas_xform); } - RID viewport_canvas_item = viewport->get_canvas_item(); - if (canvas_item) { + RID viewport_ci = viewport->get_canvas_item(); + if (ci) { real_t offset = 0; Ref<Texture2D> lock = get_theme_icon(SNAME("LockViewport"), SNAME("EditorIcons")); if (p_node->has_meta("_edit_lock_") && show_edit_locks) { - lock->draw(viewport_canvas_item, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0)); + lock->draw(viewport_ci, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0)); offset += lock->get_size().x; } Ref<Texture2D> group = get_theme_icon(SNAME("GroupViewport"), SNAME("EditorIcons")); - if (canvas_item->has_meta("_edit_group_") && show_edit_locks) { - group->draw(viewport_canvas_item, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0)); + if (ci->has_meta("_edit_group_") && show_edit_locks) { + group->draw(viewport_ci, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0)); //offset += group->get_size().x; } } @@ -3642,8 +3726,8 @@ void CanvasItemEditor::_draw_viewport() { // Update the transform transform = Transform2D(); transform.scale_basis(Size2(zoom, zoom)); - transform.elements[2] = -view_offset * zoom; - editor->get_scene_root()->set_global_canvas_transform(transform); + transform.columns[2] = -view_offset * zoom; + EditorNode::get_singleton()->get_scene_root()->set_global_canvas_transform(transform); // hide/show buttons depending on the selection bool all_locked = true; @@ -3677,20 +3761,20 @@ void CanvasItemEditor::_draw_viewport() { _draw_grid(); _draw_ruler_tool(); _draw_axis(); - if (editor->get_edited_scene()) { - _draw_locks_and_groups(editor->get_edited_scene()); - _draw_invisible_nodes_positions(editor->get_edited_scene()); + if (EditorNode::get_singleton()->get_edited_scene()) { + _draw_locks_and_groups(EditorNode::get_singleton()->get_edited_scene()); + _draw_invisible_nodes_positions(EditorNode::get_singleton()->get_edited_scene()); } _draw_selection(); RID ci = viewport->get_canvas_item(); RenderingServer::get_singleton()->canvas_item_add_set_transform(ci, Transform2D()); - EditorPluginList *over_plugin_list = editor->get_editor_plugins_over(); + EditorPluginList *over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_over(); if (!over_plugin_list->is_empty()) { over_plugin_list->forward_canvas_draw_over_viewport(viewport); } - EditorPluginList *force_over_plugin_list = editor->get_editor_plugins_force_over(); + EditorPluginList *force_over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_force_over(); if (!force_over_plugin_list->is_empty()) { force_over_plugin_list->forward_canvas_force_draw_over_viewport(viewport); } @@ -3704,272 +3788,182 @@ void CanvasItemEditor::_draw_viewport() { _draw_smart_snapping(); _draw_focus(); _draw_hover(); + _draw_transform_message(); } void CanvasItemEditor::update_viewport() { _update_scrollbars(); - viewport->update(); + viewport->queue_redraw(); } void CanvasItemEditor::set_current_tool(Tool p_tool) { _button_tool_select(p_tool); } +void CanvasItemEditor::_update_editor_settings() { + select_button->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + list_select_button->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons"))); + move_button->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"))); + scale_button->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons"))); + rotate_button->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons"))); + smart_snap_button->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); + grid_snap_button->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + snap_config_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + skeleton_menu->set_icon(get_theme_icon(SNAME("Bone"), SNAME("EditorIcons"))); + override_camera_button->set_icon(get_theme_icon(SNAME("Camera2D"), SNAME("EditorIcons"))); + pan_button->set_icon(get_theme_icon(SNAME("ToolPan"), SNAME("EditorIcons"))); + ruler_button->set_icon(get_theme_icon(SNAME("Ruler"), SNAME("EditorIcons"))); + pivot_button->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); + select_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); + anchor_handle = get_theme_icon(SNAME("EditorControlAnchor"), SNAME("EditorIcons")); + lock_button->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons"))); + unlock_button->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons"))); + group_button->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); + ungroup_button->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); + key_loc_button->set_icon(get_theme_icon(SNAME("KeyPosition"), SNAME("EditorIcons"))); + key_rot_button->set_icon(get_theme_icon(SNAME("KeyRotation"), SNAME("EditorIcons"))); + key_scale_button->set_icon(get_theme_icon(SNAME("KeyScale"), SNAME("EditorIcons"))); + key_insert_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); + key_auto_insert_button->set_icon(get_theme_icon(SNAME("AutoKey"), SNAME("EditorIcons"))); + // Use a different color for the active autokey icon to make them easier + // to distinguish from the other key icons at the top. On a light theme, + // the icon will be dark, so we need to lighten it before blending it + // with the red color. + const Color key_auto_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25); + key_auto_insert_button->add_theme_color_override("icon_pressed_color", key_auto_color.lerp(Color(1, 0, 0), 0.55)); + animation_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + + context_menu_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("ContextualToolbar"), SNAME("EditorStyles"))); + + panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/2d_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); + panner->set_scroll_speed(EDITOR_GET("editors/panning/2d_editor_pan_speed")); + warped_panning = bool(EDITOR_GET("editors/panning/warped_mouse_panning")); +} + void CanvasItemEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_PHYSICS_PROCESS) { - EditorNode::get_singleton()->get_scene_root()->set_snap_controls_to_pixels(GLOBAL_GET("gui/common/snap_controls_to_pixels")); + switch (p_what) { + case NOTIFICATION_PHYSICS_PROCESS: { + EditorNode::get_singleton()->get_scene_root()->set_snap_controls_to_pixels(GLOBAL_GET("gui/common/snap_controls_to_pixels")); - bool has_container_parents = false; - int nb_control = 0; - int nb_having_pivot = 0; + int nb_having_pivot = 0; - // Update the viewport if the canvas_item changes - List<CanvasItem *> selection = _get_edited_canvas_items(true); - for (CanvasItem *canvas_item : selection) { - CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); + // Update the viewport if the canvas_item changes + List<CanvasItem *> selection = _get_edited_canvas_items(true); + for (CanvasItem *ci : selection) { + CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci); - Rect2 rect; - if (canvas_item->_edit_use_rect()) { - rect = canvas_item->_edit_get_rect(); - } else { - rect = Rect2(); - } - Transform2D xform = canvas_item->get_transform(); + Rect2 rect; + if (ci->_edit_use_rect()) { + rect = ci->_edit_get_rect(); + } else { + rect = Rect2(); + } + Transform2D xform = ci->get_transform(); - if (rect != se->prev_rect || xform != se->prev_xform) { - viewport->update(); - se->prev_rect = rect; - se->prev_xform = xform; - } + if (rect != se->prev_rect || xform != se->prev_xform) { + viewport->queue_redraw(); + se->prev_rect = rect; + se->prev_xform = xform; + } - Control *control = Object::cast_to<Control>(canvas_item); - if (control) { - real_t anchors[4]; - Vector2 pivot; - - pivot = control->get_pivot_offset(); - anchors[SIDE_LEFT] = control->get_anchor(SIDE_LEFT); - anchors[SIDE_RIGHT] = control->get_anchor(SIDE_RIGHT); - anchors[SIDE_TOP] = control->get_anchor(SIDE_TOP); - anchors[SIDE_BOTTOM] = control->get_anchor(SIDE_BOTTOM); - - if (pivot != se->prev_pivot || anchors[SIDE_LEFT] != se->prev_anchors[SIDE_LEFT] || anchors[SIDE_RIGHT] != se->prev_anchors[SIDE_RIGHT] || anchors[SIDE_TOP] != se->prev_anchors[SIDE_TOP] || anchors[SIDE_BOTTOM] != se->prev_anchors[SIDE_BOTTOM]) { - se->prev_pivot = pivot; - se->prev_anchors[SIDE_LEFT] = anchors[SIDE_LEFT]; - se->prev_anchors[SIDE_RIGHT] = anchors[SIDE_RIGHT]; - se->prev_anchors[SIDE_TOP] = anchors[SIDE_TOP]; - se->prev_anchors[SIDE_BOTTOM] = anchors[SIDE_BOTTOM]; - viewport->update(); + Control *control = Object::cast_to<Control>(ci); + if (control) { + real_t anchors[4]; + Vector2 pivot; + + pivot = control->get_pivot_offset(); + anchors[SIDE_LEFT] = control->get_anchor(SIDE_LEFT); + anchors[SIDE_RIGHT] = control->get_anchor(SIDE_RIGHT); + anchors[SIDE_TOP] = control->get_anchor(SIDE_TOP); + anchors[SIDE_BOTTOM] = control->get_anchor(SIDE_BOTTOM); + + if (pivot != se->prev_pivot || anchors[SIDE_LEFT] != se->prev_anchors[SIDE_LEFT] || anchors[SIDE_RIGHT] != se->prev_anchors[SIDE_RIGHT] || anchors[SIDE_TOP] != se->prev_anchors[SIDE_TOP] || anchors[SIDE_BOTTOM] != se->prev_anchors[SIDE_BOTTOM]) { + se->prev_pivot = pivot; + se->prev_anchors[SIDE_LEFT] = anchors[SIDE_LEFT]; + se->prev_anchors[SIDE_RIGHT] = anchors[SIDE_RIGHT]; + se->prev_anchors[SIDE_TOP] = anchors[SIDE_TOP]; + se->prev_anchors[SIDE_BOTTOM] = anchors[SIDE_BOTTOM]; + viewport->queue_redraw(); + } } - nb_control++; - if (Object::cast_to<Container>(control->get_parent())) { - has_container_parents = true; + if (ci->_edit_use_pivot()) { + nb_having_pivot++; } } - if (canvas_item->_edit_use_pivot()) { - nb_having_pivot++; - } - } + // Activate / Deactivate the pivot tool + pivot_button->set_disabled(nb_having_pivot == 0); - // Activate / Deactivate the pivot tool - pivot_button->set_disabled(nb_having_pivot == 0); + // Update the viewport if bones changes + for (KeyValue<BoneKey, BoneList> &E : bone_list) { + Object *b = ObjectDB::get_instance(E.key.from); + if (!b) { + viewport->queue_redraw(); + break; + } - // Show / Hide the layout and anchors mode buttons - if (nb_control > 0 && nb_control == selection.size()) { - presets_menu->set_visible(true); - anchor_mode_button->set_visible(true); + Node2D *b2 = Object::cast_to<Node2D>(b); + if (!b2 || !b2->is_inside_tree()) { + continue; + } - // Disable if the selected node is child of a container - if (has_container_parents) { - presets_menu->set_disabled(true); - presets_menu->set_tooltip(TTR("Children of containers have their anchors and margins values overridden by their parent.")); - anchor_mode_button->set_disabled(true); - anchor_mode_button->set_tooltip(TTR("Children of containers have their anchors and margins values overridden by their parent.")); - } else { - presets_menu->set_disabled(false); - presets_menu->set_tooltip(TTR("Presets for the anchors and margins values of a Control node.")); - anchor_mode_button->set_disabled(false); - anchor_mode_button->set_tooltip(TTR("When active, moving Control nodes changes their anchors instead of their margins.")); - } - } else { - presets_menu->set_visible(false); - anchor_mode_button->set_visible(false); - } + Transform2D global_xform = b2->get_global_transform(); - // Update the viewport if bones changes - for (KeyValue<BoneKey, BoneList> &E : bone_list) { - Object *b = ObjectDB::get_instance(E.key.from); - if (!b) { - viewport->update(); - break; - } + if (global_xform != E.value.xform) { + E.value.xform = global_xform; + viewport->queue_redraw(); + } - Node2D *b2 = Object::cast_to<Node2D>(b); - if (!b2 || !b2->is_inside_tree()) { - continue; + Bone2D *bone = Object::cast_to<Bone2D>(b); + if (bone && bone->get_length() != E.value.length) { + E.value.length = bone->get_length(); + viewport->queue_redraw(); + } } + } break; - Transform2D global_xform = b2->get_global_transform(); + case NOTIFICATION_ENTER_TREE: { + select_sb->set_texture(get_theme_icon(SNAME("EditorRect2D"), SNAME("EditorIcons"))); + select_sb->set_texture_margin_all(4); + select_sb->set_content_margin_all(4); - if (global_xform != E.value.xform) { - E.value.xform = global_xform; - viewport->update(); - } + AnimationPlayerEditor::get_singleton()->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); + _keying_changed(); + _update_editor_settings(); + } break; - Bone2D *bone = Object::cast_to<Bone2D>(b); - if (bone && bone->get_length() != E.value.length) { - E.value.length = bone->get_length(); - viewport->update(); - } - } - } + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + select_sb->set_texture(get_theme_icon(SNAME("EditorRect2D"), SNAME("EditorIcons"))); + _update_editor_settings(); + } break; - if (p_what == NOTIFICATION_ENTER_TREE) { - select_sb->set_texture(get_theme_icon(SNAME("EditorRect2D"), SNAME("EditorIcons"))); - for (int i = 0; i < 4; i++) { - select_sb->set_margin_size(Side(i), 4); - select_sb->set_default_margin(Side(i), 4); - } - - AnimationPlayerEditor::get_singleton()->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); - _keying_changed(); - - } else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { - select_sb->set_texture(get_theme_icon(SNAME("EditorRect2D"), SNAME("EditorIcons"))); - } - - if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { - select_button->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); - list_select_button->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons"))); - move_button->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"))); - scale_button->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons"))); - rotate_button->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons"))); - smart_snap_button->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); - grid_snap_button->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); - snap_config_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); - skeleton_menu->set_icon(get_theme_icon(SNAME("Bone"), SNAME("EditorIcons"))); - override_camera_button->set_icon(get_theme_icon(SNAME("Camera2D"), SNAME("EditorIcons"))); - pan_button->set_icon(get_theme_icon(SNAME("ToolPan"), SNAME("EditorIcons"))); - ruler_button->set_icon(get_theme_icon(SNAME("Ruler"), SNAME("EditorIcons"))); - pivot_button->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); - select_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); - anchor_handle = get_theme_icon(SNAME("EditorControlAnchor"), SNAME("EditorIcons")); - lock_button->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons"))); - unlock_button->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons"))); - group_button->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); - ungroup_button->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); - key_loc_button->set_icon(get_theme_icon(SNAME("KeyPosition"), SNAME("EditorIcons"))); - key_rot_button->set_icon(get_theme_icon(SNAME("KeyRotation"), SNAME("EditorIcons"))); - key_scale_button->set_icon(get_theme_icon(SNAME("KeyScale"), SNAME("EditorIcons"))); - key_insert_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); - key_auto_insert_button->set_icon(get_theme_icon(SNAME("AutoKey"), SNAME("EditorIcons"))); - // Use a different color for the active autokey icon to make them easier - // to distinguish from the other key icons at the top. On a light theme, - // the icon will be dark, so we need to lighten it before blending it - // with the red color. - const Color key_auto_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25); - key_auto_insert_button->add_theme_color_override("icon_pressed_color", key_auto_color.lerp(Color(1, 0, 0), 0.55)); - animation_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); - - _update_context_menu_stylebox(); - - presets_menu->set_icon(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); - - PopupMenu *p = presets_menu->get_popup(); - - p->clear(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftCenter"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopCenter"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightCenter"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomCenter"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_AND_OFFSETS_PRESET_CENTER); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlVcenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlHcenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_AND_OFFSETS_PRESET_WIDE); - p->add_icon_item(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")), TTR("Keep Ratio"), ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO); - p->add_separator(); - p->add_submenu_item(TTR("Anchors only"), "Anchors"); - p->set_item_icon(21, get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); - - anchors_popup->clear(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_PRESET_TOP_LEFT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_PRESET_TOP_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_PRESET_BOTTOM_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_PRESET_BOTTOM_LEFT); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftCenter"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_PRESET_CENTER_LEFT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopCenter"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_PRESET_CENTER_TOP); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightCenter"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_PRESET_CENTER_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomCenter"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_PRESET_CENTER_BOTTOM); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_PRESET_CENTER); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_PRESET_LEFT_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_PRESET_TOP_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_PRESET_RIGHT_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_PRESET_BOTTOM_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlVcenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_PRESET_VCENTER_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlHcenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_PRESET_HCENTER_WIDE); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_WIDE); - - anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); - } - - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (!is_visible() && override_camera_button->is_pressed()) { - EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton(); - - debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE); - override_camera_button->set_pressed(false); - } + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible() && override_camera_button->is_pressed()) { + EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton(); + + debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE); + override_camera_button->set_pressed(false); + } + } break; } } void CanvasItemEditor::_selection_changed() { - // Update the anchors_mode - int nbValidControls = 0; - int nbAnchorsMode = 0; - List<Node *> selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (!control) { - continue; - } - if (Object::cast_to<Container>(control->get_parent())) { - continue; - } - - nbValidControls++; - if (control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_")) { - nbAnchorsMode++; - } - } - anchors_mode = (nbValidControls == nbAnchorsMode); - anchor_mode_button->set_pressed(anchors_mode); - if (!selected_from_canvas) { - drag_type = DRAG_NONE; + _reset_drag(); } selected_from_canvas = false; } void CanvasItemEditor::edit(CanvasItem *p_canvas_item) { + if (!p_canvas_item) { + return; + } + Array selection = editor_selection->get_selected_nodes(); - if (selection.size() != 1 || (Node *)selection[0] != p_canvas_item) { - drag_type = DRAG_NONE; + if (selection.size() != 1 || Object::cast_to<Node>(selection[0]) != p_canvas_item) { + _reset_drag(); // Clear the selection editor_selection->clear(); //_clear_canvas_items(); @@ -3977,18 +3971,6 @@ void CanvasItemEditor::edit(CanvasItem *p_canvas_item) { } } -void CanvasItemEditor::_update_context_menu_stylebox() { - // This must be called when the theme changes to follow the new accent color. - Ref<StyleBoxFlat> context_menu_stylebox = memnew(StyleBoxFlat); - const Color accent_color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("accent_color"), SNAME("Editor")); - context_menu_stylebox->set_bg_color(accent_color * Color(1, 1, 1, 0.1)); - // Add an underline to the StyleBox, but prevent its minimum vertical size from changing. - context_menu_stylebox->set_border_color(accent_color); - context_menu_stylebox->set_border_width(SIDE_BOTTOM, Math::round(2 * EDSCALE)); - context_menu_stylebox->set_default_margin(SIDE_BOTTOM, 0); - context_menu_container->add_theme_style_override("panel", context_menu_stylebox); -} - void CanvasItemEditor::_update_scrollbars() { updating_scroll = true; @@ -4001,13 +3983,13 @@ void CanvasItemEditor::_update_scrollbars() { Size2 vmin = v_scroll->get_minimum_size(); // Get the visible frame. - Size2 screen_rect = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height")); + Size2 screen_rect = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); Rect2 local_rect = Rect2(Point2(), viewport->get_size() - Size2(vmin.width, hmin.height)); // Calculate scrollable area. Rect2 canvas_item_rect = Rect2(Point2(), screen_rect); - if (editor->is_inside_tree() && editor->get_edited_scene()) { - Rect2 content_rect = _get_encompassing_rect(editor->get_edited_scene()); + if (EditorNode::get_singleton()->is_inside_tree() && EditorNode::get_singleton()->get_edited_scene()) { + Rect2 content_rect = _get_encompassing_rect(EditorNode::get_singleton()->get_edited_scene()); canvas_item_rect.expand_to(content_rect.position); canvas_item_rect.expand_to(content_rect.position + content_rect.size); } @@ -4018,7 +4000,7 @@ void CanvasItemEditor::_update_scrollbars() { Size2 size = viewport->get_size(); Point2 begin = canvas_item_rect.position; Point2 end = canvas_item_rect.position + canvas_item_rect.size - local_rect.size / zoom; - bool constrain_editor_view = bool(EditorSettings::get_singleton()->get("editors/2d/constrain_editor_view")); + bool constrain_editor_view = bool(EDITOR_GET("editors/2d/constrain_editor_view")); if (canvas_item_rect.size.height <= (local_rect.size.y / zoom)) { real_t centered = -(size.y / 2) / zoom + screen_rect.y / 2; @@ -4088,91 +4070,7 @@ void CanvasItemEditor::_update_scroll(real_t) { view_offset.x = h_scroll->get_value(); view_offset.y = v_scroll->get_value(); - viewport->update(); -} - -void CanvasItemEditor::_set_anchors_and_offsets_preset(Control::LayoutPreset p_preset) { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Anchors and Offsets")); - - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - undo_redo->add_do_method(control, "set_anchors_preset", p_preset); - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_TOP_RIGHT: - case PRESET_BOTTOM_LEFT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_LEFT: - case PRESET_CENTER_TOP: - case PRESET_CENTER_RIGHT: - case PRESET_CENTER_BOTTOM: - case PRESET_CENTER: - undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_KEEP_SIZE); - break; - case PRESET_LEFT_WIDE: - case PRESET_TOP_WIDE: - case PRESET_RIGHT_WIDE: - case PRESET_BOTTOM_WIDE: - case PRESET_VCENTER_WIDE: - case PRESET_HCENTER_WIDE: - case PRESET_WIDE: - undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_MINSIZE); - break; - } - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - } - } - - undo_redo->commit_action(); - - anchors_mode = false; - anchor_mode_button->set_pressed(anchors_mode); -} - -void CanvasItemEditor::_set_anchors_and_offsets_to_keep_ratio() { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Anchors and Offsets")); - - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - Point2 top_left_anchor = _position_to_anchor(control, Point2()); - Point2 bottom_right_anchor = _position_to_anchor(control, control->get_size()); - undo_redo->add_do_method(control, "set_anchor", SIDE_LEFT, top_left_anchor.x, false, true); - undo_redo->add_do_method(control, "set_anchor", SIDE_RIGHT, bottom_right_anchor.x, false, true); - undo_redo->add_do_method(control, "set_anchor", SIDE_TOP, top_left_anchor.y, false, true); - undo_redo->add_do_method(control, "set_anchor", SIDE_BOTTOM, bottom_right_anchor.y, false, true); - undo_redo->add_do_method(control, "set_meta", "_edit_use_anchors_", true); - - bool use_anchors = control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_"); - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - undo_redo->add_undo_method(control, "set_meta", "_edit_use_anchors_", use_anchors); - - anchors_mode = true; - anchor_mode_button->set_pressed(anchors_mode); - } - } - - undo_redo->commit_action(); -} - -void CanvasItemEditor::_set_anchors_preset(Control::LayoutPreset p_preset) { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Anchors")); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - undo_redo->add_do_method(control, "set_anchors_preset", p_preset); - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - } - } - - undo_redo->commit_action(); + viewport->queue_redraw(); } void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) { @@ -4208,14 +4106,18 @@ void CanvasItemEditor::_update_zoom(real_t p_zoom) { _zoom_on_position(p_zoom, viewport_scrollable->get_size() / 2.0); } +void CanvasItemEditor::_shortcut_zoom_set(real_t p_zoom) { + _zoom_on_position(p_zoom * MAX(1, EDSCALE), viewport->get_local_mouse_position()); +} + void CanvasItemEditor::_button_toggle_smart_snap(bool p_status) { smart_snap_active = p_status; - viewport->update(); + viewport->queue_redraw(); } void CanvasItemEditor::_button_toggle_grid_snap(bool p_status) { grid_snap_active = p_status; - viewport->update(); + viewport->queue_redraw(); } void CanvasItemEditor::_button_override_camera(bool p_pressed) { @@ -4236,34 +4138,36 @@ void CanvasItemEditor::_button_tool_select(int p_index) { tool = (Tool)p_index; - viewport->update(); + viewport->queue_redraw(); _update_cursor(); } void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) { - Map<Node *, Object *> &selection = editor_selection->get_selection(); + const HashMap<Node *, Object *> &selection = editor_selection->get_selection(); + AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); + te->make_insert_queue(); for (const KeyValue<Node *, Object *> &E : selection) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); - if (!canvas_item || !canvas_item->is_visible_in_tree()) { + CanvasItem *ci = Object::cast_to<CanvasItem>(E.key); + if (!ci || !ci->is_visible_in_tree()) { continue; } - if (canvas_item->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { + if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { continue; } - if (Object::cast_to<Node2D>(canvas_item)) { - Node2D *n2d = Object::cast_to<Node2D>(canvas_item); + if (Object::cast_to<Node2D>(ci)) { + Node2D *n2d = Object::cast_to<Node2D>(ci); if (key_pos && p_location) { - AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing); + te->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing); } if (key_rot && p_rotation) { - AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing); + te->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing); } if (key_scale && p_scale) { - AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing); + te->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing); } if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) { @@ -4289,92 +4193,73 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, if (has_chain && ik_chain.size()) { for (Node2D *&F : ik_chain) { if (key_pos) { - AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "position", F->get_position(), p_on_existing); + te->insert_node_value_key(F, "position", F->get_position(), p_on_existing); } if (key_rot) { - AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing); + te->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing); } if (key_scale) { - AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing); + te->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing); } } } } - } else if (Object::cast_to<Control>(canvas_item)) { - Control *ctrl = Object::cast_to<Control>(canvas_item); + } else if (Object::cast_to<Control>(ci)) { + Control *ctrl = Object::cast_to<Control>(ci); if (key_pos) { - AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing); + te->insert_node_value_key(ctrl, "position", ctrl->get_position(), p_on_existing); } if (key_rot) { - AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation(), p_on_existing); + te->insert_node_value_key(ctrl, "rotation", ctrl->get_rotation(), p_on_existing); } if (key_scale) { - AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing); + te->insert_node_value_key(ctrl, "size", ctrl->get_size(), p_on_existing); } } } -} - -void CanvasItemEditor::_button_toggle_anchor_mode(bool p_status) { - List<CanvasItem *> selection = _get_edited_canvas_items(false, false); - for (CanvasItem *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (!control || Object::cast_to<Container>(control->get_parent())) { - continue; - } - - control->set_meta("_edit_use_anchors_", p_status); - } - - anchors_mode = p_status; - viewport->update(); + te->commit_insert_queue(); } void CanvasItemEditor::_update_override_camera_button(bool p_game_running) { if (p_game_running) { override_camera_button->set_disabled(false); - override_camera_button->set_tooltip(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera.")); + override_camera_button->set_tooltip_text(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera.")); } else { override_camera_button->set_disabled(true); override_camera_button->set_pressed(false); - override_camera_button->set_tooltip(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature.")); + override_camera_button->set_tooltip_text(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature.")); } } void CanvasItemEditor::_popup_callback(int p_op) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); last_option = MenuOption(p_op); switch (p_op) { - case SHOW_GRID: { - show_grid = !show_grid; - int idx = view_menu->get_popup()->get_item_index(SHOW_GRID); - view_menu->get_popup()->set_item_checked(idx, show_grid); - viewport->update(); - } break; case SHOW_ORIGIN: { show_origin = !show_origin; int idx = view_menu->get_popup()->get_item_index(SHOW_ORIGIN); view_menu->get_popup()->set_item_checked(idx, show_origin); - viewport->update(); + viewport->queue_redraw(); } break; case SHOW_VIEWPORT: { show_viewport = !show_viewport; int idx = view_menu->get_popup()->get_item_index(SHOW_VIEWPORT); view_menu->get_popup()->set_item_checked(idx, show_viewport); - viewport->update(); + viewport->queue_redraw(); } break; case SHOW_EDIT_LOCKS: { show_edit_locks = !show_edit_locks; int idx = view_menu->get_popup()->get_item_index(SHOW_EDIT_LOCKS); view_menu->get_popup()->set_item_checked(idx, show_edit_locks); - viewport->update(); + viewport->queue_redraw(); } break; case SHOW_TRANSFORMATION_GIZMOS: { show_transformation_gizmos = !show_transformation_gizmos; int idx = view_menu->get_popup()->get_item_index(SHOW_TRANSFORMATION_GIZMOS); view_menu->get_popup()->set_item_checked(idx, show_transformation_gizmos); - viewport->update(); + viewport->queue_redraw(); } break; case SNAP_USE_NODE_PARENT: { snap_node_parent = !snap_node_parent; @@ -4420,7 +4305,7 @@ void CanvasItemEditor::_popup_callback(int p_op) { snap_relative = !snap_relative; int idx = snap_config_menu->get_popup()->get_item_index(SNAP_RELATIVE); snap_config_menu->get_popup()->set_item_checked(idx, snap_relative); - viewport->update(); + viewport->queue_redraw(); } break; case SNAP_USE_PIXEL: { snap_pixel = !snap_pixel; @@ -4428,7 +4313,7 @@ void CanvasItemEditor::_popup_callback(int p_op) { snap_config_menu->get_popup()->set_item_checked(idx, snap_pixel); } break; case SNAP_CONFIGURE: { - ((SnapDialog *)snap_dialog)->set_fields(grid_offset, grid_step, primary_grid_steps, snap_rotation_offset, snap_rotation_step, snap_scale_step); + static_cast<SnapDialog *>(snap_dialog)->set_fields(grid_offset, grid_step, primary_grid_steps, snap_rotation_offset, snap_rotation_step, snap_scale_step); snap_dialog->popup_centered(Size2(220, 160) * EDSCALE); } break; case SKELETON_SHOW_BONES: { @@ -4450,41 +4335,40 @@ void CanvasItemEditor::_popup_callback(int p_op) { show_helpers = !show_helpers; int idx = view_menu->get_popup()->get_item_index(SHOW_HELPERS); view_menu->get_popup()->set_item_checked(idx, show_helpers); - viewport->update(); + viewport->queue_redraw(); } break; case SHOW_RULERS: { show_rulers = !show_rulers; int idx = view_menu->get_popup()->get_item_index(SHOW_RULERS); view_menu->get_popup()->set_item_checked(idx, show_rulers); - _update_scrollbars(); - viewport->update(); + update_viewport(); } break; case SHOW_GUIDES: { show_guides = !show_guides; int idx = view_menu->get_popup()->get_item_index(SHOW_GUIDES); view_menu->get_popup()->set_item_checked(idx, show_guides); - viewport->update(); + viewport->queue_redraw(); } break; case LOCK_SELECTED: { undo_redo->create_action(TTR("Lock Selected")); List<Node *> selection = editor_selection->get_selected_node_list(); for (Node *E : selection) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E); - if (!canvas_item || !canvas_item->is_inside_tree()) { + CanvasItem *ci = Object::cast_to<CanvasItem>(E); + if (!ci || !ci->is_inside_tree()) { continue; } - if (canvas_item->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { + if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { continue; } - undo_redo->add_do_method(canvas_item, "set_meta", "_edit_lock_", true); - undo_redo->add_undo_method(canvas_item, "remove_meta", "_edit_lock_"); + undo_redo->add_do_method(ci, "set_meta", "_edit_lock_", true); + undo_redo->add_undo_method(ci, "remove_meta", "_edit_lock_"); undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed"); undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed"); } - undo_redo->add_do_method(viewport, "update", Variant()); - undo_redo->add_undo_method(viewport, "update", Variant()); + undo_redo->add_do_method(viewport, "queue_redraw"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } break; case UNLOCK_SELECTED: { @@ -4492,21 +4376,21 @@ void CanvasItemEditor::_popup_callback(int p_op) { List<Node *> selection = editor_selection->get_selected_node_list(); for (Node *E : selection) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E); - if (!canvas_item || !canvas_item->is_inside_tree()) { + CanvasItem *ci = Object::cast_to<CanvasItem>(E); + if (!ci || !ci->is_inside_tree()) { continue; } - if (canvas_item->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { + if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { continue; } - undo_redo->add_do_method(canvas_item, "remove_meta", "_edit_lock_"); - undo_redo->add_undo_method(canvas_item, "set_meta", "_edit_lock_", true); + undo_redo->add_do_method(ci, "remove_meta", "_edit_lock_"); + undo_redo->add_undo_method(ci, "set_meta", "_edit_lock_", true); undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed"); undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed"); } - undo_redo->add_do_method(viewport, "update", Variant()); - undo_redo->add_undo_method(viewport, "update", Variant()); + undo_redo->add_do_method(viewport, "queue_redraw"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } break; case GROUP_SELECTED: { @@ -4514,21 +4398,21 @@ void CanvasItemEditor::_popup_callback(int p_op) { List<Node *> selection = editor_selection->get_selected_node_list(); for (Node *E : selection) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E); - if (!canvas_item || !canvas_item->is_inside_tree()) { + CanvasItem *ci = Object::cast_to<CanvasItem>(E); + if (!ci || !ci->is_inside_tree()) { continue; } - if (canvas_item->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { + if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { continue; } - undo_redo->add_do_method(canvas_item, "set_meta", "_edit_group_", true); - undo_redo->add_undo_method(canvas_item, "remove_meta", "_edit_group_"); + undo_redo->add_do_method(ci, "set_meta", "_edit_group_", true); + undo_redo->add_undo_method(ci, "remove_meta", "_edit_group_"); undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed"); undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed"); } - undo_redo->add_do_method(viewport, "update", Variant()); - undo_redo->add_undo_method(viewport, "update", Variant()); + undo_redo->add_do_method(viewport, "queue_redraw"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } break; case UNGROUP_SELECTED: { @@ -4536,123 +4420,23 @@ void CanvasItemEditor::_popup_callback(int p_op) { List<Node *> selection = editor_selection->get_selected_node_list(); for (Node *E : selection) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E); - if (!canvas_item || !canvas_item->is_inside_tree()) { + CanvasItem *ci = Object::cast_to<CanvasItem>(E); + if (!ci || !ci->is_inside_tree()) { continue; } - if (canvas_item->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { + if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { continue; } - undo_redo->add_do_method(canvas_item, "remove_meta", "_edit_group_"); - undo_redo->add_undo_method(canvas_item, "set_meta", "_edit_group_", true); + undo_redo->add_do_method(ci, "remove_meta", "_edit_group_"); + undo_redo->add_undo_method(ci, "set_meta", "_edit_group_", true); undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed"); undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed"); } - undo_redo->add_do_method(viewport, "update", Variant()); - undo_redo->add_undo_method(viewport, "update", Variant()); + undo_redo->add_do_method(viewport, "queue_redraw"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT: { - _set_anchors_and_offsets_preset(PRESET_TOP_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_TOP_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT: { - _set_anchors_and_offsets_preset(PRESET_CENTER_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_CENTER_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP: { - _set_anchors_and_offsets_preset(PRESET_CENTER_TOP); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM: { - _set_anchors_and_offsets_preset(PRESET_CENTER_BOTTOM); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER: { - _set_anchors_and_offsets_preset(PRESET_CENTER); - } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE: { - _set_anchors_and_offsets_preset(PRESET_TOP_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE: { - _set_anchors_and_offsets_preset(PRESET_LEFT_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE: { - _set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE: { - _set_anchors_and_offsets_preset(PRESET_VCENTER_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE: { - _set_anchors_and_offsets_preset(PRESET_HCENTER_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_WIDE: { - _set_anchors_and_offsets_preset(Control::PRESET_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO: { - _set_anchors_and_offsets_to_keep_ratio(); - } break; - - case ANCHORS_PRESET_TOP_LEFT: { - _set_anchors_preset(PRESET_TOP_LEFT); - } break; - case ANCHORS_PRESET_TOP_RIGHT: { - _set_anchors_preset(PRESET_TOP_RIGHT); - } break; - case ANCHORS_PRESET_BOTTOM_LEFT: { - _set_anchors_preset(PRESET_BOTTOM_LEFT); - } break; - case ANCHORS_PRESET_BOTTOM_RIGHT: { - _set_anchors_preset(PRESET_BOTTOM_RIGHT); - } break; - case ANCHORS_PRESET_CENTER_LEFT: { - _set_anchors_preset(PRESET_CENTER_LEFT); - } break; - case ANCHORS_PRESET_CENTER_RIGHT: { - _set_anchors_preset(PRESET_CENTER_RIGHT); - } break; - case ANCHORS_PRESET_CENTER_TOP: { - _set_anchors_preset(PRESET_CENTER_TOP); - } break; - case ANCHORS_PRESET_CENTER_BOTTOM: { - _set_anchors_preset(PRESET_CENTER_BOTTOM); - } break; - case ANCHORS_PRESET_CENTER: { - _set_anchors_preset(PRESET_CENTER); - } break; - case ANCHORS_PRESET_TOP_WIDE: { - _set_anchors_preset(PRESET_TOP_WIDE); - } break; - case ANCHORS_PRESET_LEFT_WIDE: { - _set_anchors_preset(PRESET_LEFT_WIDE); - } break; - case ANCHORS_PRESET_RIGHT_WIDE: { - _set_anchors_preset(PRESET_RIGHT_WIDE); - } break; - case ANCHORS_PRESET_BOTTOM_WIDE: { - _set_anchors_preset(PRESET_BOTTOM_WIDE); - } break; - case ANCHORS_PRESET_VCENTER_WIDE: { - _set_anchors_preset(PRESET_VCENTER_WIDE); - } break; - case ANCHORS_PRESET_HCENTER_WIDE: { - _set_anchors_preset(PRESET_HCENTER_WIDE); - } break; - case ANCHORS_PRESET_WIDE: { - _set_anchors_preset(Control::PRESET_WIDE); - } break; case ANIM_INSERT_KEY: case ANIM_INSERT_KEY_EXISTING: { @@ -4673,20 +4457,20 @@ void CanvasItemEditor::_popup_callback(int p_op) { case ANIM_COPY_POSE: { pose_clipboard.clear(); - Map<Node *, Object *> &selection = editor_selection->get_selection(); + const HashMap<Node *, Object *> &selection = editor_selection->get_selection(); for (const KeyValue<Node *, Object *> &E : selection) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); - if (!canvas_item || !canvas_item->is_visible_in_tree()) { + CanvasItem *ci = Object::cast_to<CanvasItem>(E.key); + if (!ci || !ci->is_visible_in_tree()) { continue; } - if (canvas_item->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { + if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { continue; } - if (Object::cast_to<Node2D>(canvas_item)) { - Node2D *n2d = Object::cast_to<Node2D>(canvas_item); + if (Object::cast_to<Node2D>(ci)) { + Node2D *n2d = Object::cast_to<Node2D>(ci); PoseClipboard pc; pc.pos = n2d->get_position(); pc.rot = n2d->get_rotation(); @@ -4719,20 +4503,20 @@ void CanvasItemEditor::_popup_callback(int p_op) { } break; case ANIM_CLEAR_POSE: { - Map<Node *, Object *> &selection = editor_selection->get_selection(); + HashMap<Node *, Object *> &selection = editor_selection->get_selection(); for (const KeyValue<Node *, Object *> &E : selection) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); - if (!canvas_item || !canvas_item->is_visible_in_tree()) { + CanvasItem *ci = Object::cast_to<CanvasItem>(E.key); + if (!ci || !ci->is_visible_in_tree()) { continue; } - if (canvas_item->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { + if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { continue; } - if (Object::cast_to<Node2D>(canvas_item)) { - Node2D *n2d = Object::cast_to<Node2D>(canvas_item); + if (Object::cast_to<Node2D>(ci)) { + Node2D *n2d = Object::cast_to<Node2D>(ci); if (key_pos) { n2d->set_position(Vector2()); @@ -4743,8 +4527,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { if (key_scale) { n2d->set_scale(Vector2(1, 1)); } - } else if (Object::cast_to<Control>(canvas_item)) { - Control *ctrl = Object::cast_to<Control>(canvas_item); + } else if (Object::cast_to<Control>(ci)) { + Control *ctrl = Object::cast_to<Control>(ci); if (key_pos) { ctrl->set_position(Point2()); @@ -4770,7 +4554,7 @@ void CanvasItemEditor::_popup_callback(int p_op) { undo_redo->add_do_method(root, "remove_meta", "_edit_vertical_guides_"); undo_redo->add_undo_method(root, "set_meta", "_edit_vertical_guides_", vguides); } - undo_redo->add_undo_method(viewport, "update"); + undo_redo->add_undo_method(viewport, "queue_redraw"); undo_redo->commit_action(); } @@ -4788,7 +4572,7 @@ void CanvasItemEditor::_popup_callback(int p_op) { } break; case SKELETON_MAKE_BONES: { - Map<Node *, Object *> &selection = editor_selection->get_selection(); + HashMap<Node *, Object *> &selection = editor_selection->get_selection(); Node *editor_root = EditorNode::get_singleton()->get_edited_scene()->get_tree()->get_edited_scene_root(); undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)")); @@ -4836,30 +4620,30 @@ void CanvasItemEditor::_focus_selection(int p_op) { Rect2 rect; int count = 0; - Map<Node *, Object *> &selection = editor_selection->get_selection(); + const HashMap<Node *, Object *> &selection = editor_selection->get_selection(); for (const KeyValue<Node *, Object *> &E : selection) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); - if (!canvas_item) { + CanvasItem *ci = Object::cast_to<CanvasItem>(E.key); + if (!ci) { continue; } - if (canvas_item->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { + if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { continue; } // counting invisible items, for now - //if (!canvas_item->is_visible_in_tree()) continue; + //if (!ci->is_visible_in_tree()) continue; ++count; Rect2 item_rect; - if (canvas_item->_edit_use_rect()) { - item_rect = canvas_item->_edit_get_rect(); + if (ci->_edit_use_rect()) { + item_rect = ci->_edit_get_rect(); } else { item_rect = Rect2(); } - Vector2 pos = canvas_item->get_global_transform().get_origin(); - Vector2 scale = canvas_item->get_global_transform().get_scale(); - real_t angle = canvas_item->get_global_transform().get_rotation(); + Vector2 pos = ci->get_global_transform().get_origin(); + Vector2 scale = ci->get_global_transform().get_scale(); + real_t angle = ci->get_global_transform().get_rotation(); Transform2D t(angle, Vector2(0.f, 0.f)); item_rect = t.xform(item_rect); @@ -4869,35 +4653,32 @@ void CanvasItemEditor::_focus_selection(int p_op) { } else { rect = rect.merge(canvas_item_rect); } - }; - - if (p_op == VIEW_CENTER_TO_SELECTION) { - center = rect.get_center(); - Vector2 offset = viewport->get_size() / 2 - editor->get_scene_root()->get_global_canvas_transform().xform(center); - view_offset -= (offset / zoom).round(); - update_viewport(); - - } else { // VIEW_FRAME_TO_SELECTION + } - if (rect.size.x > CMP_EPSILON && rect.size.y > CMP_EPSILON) { - real_t scale_x = viewport->get_size().x / rect.size.x; - real_t scale_y = viewport->get_size().y / rect.size.y; - zoom = scale_x < scale_y ? scale_x : scale_y; - zoom *= 0.90; - viewport->update(); - zoom_widget->set_zoom(zoom); - call_deferred(SNAME("_popup_callback"), VIEW_CENTER_TO_SELECTION); - } + if (p_op == VIEW_FRAME_TO_SELECTION && rect.size.x > CMP_EPSILON && rect.size.y > CMP_EPSILON) { + real_t scale_x = viewport->get_size().x / rect.size.x; + real_t scale_y = viewport->get_size().y / rect.size.y; + zoom = scale_x < scale_y ? scale_x : scale_y; + zoom *= 0.90; + zoom_widget->set_zoom(zoom); + viewport->queue_redraw(); // Redraw to update the global canvas transform after zoom changes. + call_deferred(SNAME("center_at"), rect.get_center()); // Defer because the updated transform is needed. + } else { + center_at(rect.get_center()); } } +void CanvasItemEditor::_reset_drag() { + drag_type = DRAG_NONE; + drag_selection.clear(); +} + void CanvasItemEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_update_override_camera_button", "game_running"), &CanvasItemEditor::_update_override_camera_button); ClassDB::bind_method("_get_editor_data", &CanvasItemEditor::_get_editor_data); ClassDB::bind_method(D_METHOD("set_state"), &CanvasItemEditor::set_state); ClassDB::bind_method(D_METHOD("update_viewport"), &CanvasItemEditor::update_viewport); - ClassDB::bind_method(D_METHOD("_zoom_on_position"), &CanvasItemEditor::_zoom_on_position); + ClassDB::bind_method(D_METHOD("center_at", "position"), &CanvasItemEditor::center_at); ClassDB::bind_method("_set_owner_for_node_and_children", &CanvasItemEditor::_set_owner_for_node_and_children); @@ -4924,7 +4705,7 @@ Dictionary CanvasItemEditor::get_state() const { state["snap_node_center"] = snap_node_center; state["snap_other_nodes"] = snap_other_nodes; state["snap_guides"] = snap_guides; - state["show_grid"] = show_grid; + state["grid_visibility"] = grid_visibility; state["show_origin"] = show_origin; state["show_viewport"] = show_viewport; state["show_rulers"] = show_rulers; @@ -5026,10 +4807,8 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) { smartsnap_config_popup->set_item_checked(idx, snap_guides); } - if (state.has("show_grid")) { - show_grid = state["show_grid"]; - int idx = view_menu->get_popup()->get_item_index(SHOW_GRID); - view_menu->get_popup()->set_item_checked(idx, show_grid); + if (state.has("grid_visibility")) { + grid_visibility = (GridVisibility)(int)(state["grid_visibility"]); } if (state.has("show_origin")) { @@ -5107,21 +4886,35 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) { if (update_scrollbars) { _update_scrollbars(); } - viewport->update(); + viewport->queue_redraw(); } void CanvasItemEditor::add_control_to_menu_panel(Control *p_control) { ERR_FAIL_COND(!p_control); - hbc_context_menu->add_child(p_control); + context_menu_hbox->add_child(p_control); } void CanvasItemEditor::remove_control_from_menu_panel(Control *p_control) { - hbc_context_menu->remove_child(p_control); + context_menu_hbox->remove_child(p_control); } -HSplitContainer *CanvasItemEditor::get_palette_split() { - return palette_split; +void CanvasItemEditor::add_control_to_left_panel(Control *p_control) { + left_panel_split->add_child(p_control); + left_panel_split->move_child(p_control, 0); +} + +void CanvasItemEditor::add_control_to_right_panel(Control *p_control) { + right_panel_split->add_child(p_control); + right_panel_split->move_child(p_control, 1); +} + +void CanvasItemEditor::remove_control_from_left_panel(Control *p_control) { + left_panel_split->remove_child(p_control); +} + +void CanvasItemEditor::remove_control_from_right_panel(Control *p_control) { + right_panel_split->remove_child(p_control); } VSplitContainer *CanvasItemEditor::get_bottom_split() { @@ -5132,92 +4925,54 @@ void CanvasItemEditor::focus_selection() { _focus_selection(VIEW_CENTER_TO_SELECTION); } -CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { - key_pos = true; - key_rot = true; - key_scale = false; +void CanvasItemEditor::center_at(const Point2 &p_pos) { + Vector2 offset = viewport->get_size() / 2 - EditorNode::get_singleton()->get_scene_root()->get_global_canvas_transform().xform(p_pos); + view_offset -= (offset / zoom).round(); + update_viewport(); +} - show_grid = false; - show_origin = true; - show_viewport = true; - show_helpers = false; - show_rulers = true; - show_guides = true; - show_transformation_gizmos = true; - show_edit_locks = true; +CanvasItemEditor::CanvasItemEditor() { zoom = 1.0 / MAX(1, EDSCALE); view_offset = Point2(-150 - RULER_WIDTH, -95 - RULER_WIDTH); previous_update_view_offset = view_offset; // Moves the view a little bit to the left so that (0,0) is visible. The values a relative to a 16/10 screen - grid_offset = Point2(); - grid_step = Point2(8, 8); // A power-of-two value works better as a default - primary_grid_steps = 8; // A power-of-two value works better as a default - grid_step_multiplier = 0; - snap_rotation_offset = 0; - snap_rotation_step = Math::deg2rad(15.0); - snap_scale_step = 0.1f; - smart_snap_active = false; - grid_snap_active = false; - snap_node_parent = true; - snap_node_anchors = true; - snap_node_sides = true; - snap_node_center = true; - snap_other_nodes = true; - snap_guides = true; - snap_rotation = false; - snap_scale = false; - snap_relative = false; - // Enable pixel snapping even if pixel snap rendering is disabled in the Project Settings. - // This results in crisper visuals by preventing 2D nodes from being placed at subpixel coordinates. - snap_pixel = true; + snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; - selected_from_canvas = false; - anchors_mode = false; - - drag_type = DRAG_NONE; - drag_from = Vector2(); - drag_to = Vector2(); - dragged_guide_pos = Point2(); - dragged_guide_index = -1; - is_hovering_h_guide = false; - is_hovering_v_guide = false; - panning = false; - pan_pressed = false; - - ruler_tool_active = false; - ruler_tool_origin = Point2(); - - bone_last_frame = 0; - - tool = TOOL_SELECT; - undo_redo = p_editor->get_undo_redo(); - editor = p_editor; - editor_selection = p_editor->get_editor_selection(); + editor_selection = EditorNode::get_singleton()->get_editor_selection(); editor_selection->add_editor_plugin(this); - editor_selection->connect("selection_changed", callable_mp((CanvasItem *)this, &CanvasItem::update)); + editor_selection->connect("selection_changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); editor_selection->connect("selection_changed", callable_mp(this, &CanvasItemEditor::_selection_changed)); - editor->get_scene_tree_dock()->connect("node_created", callable_mp(this, &CanvasItemEditor::_node_created)); - editor->get_scene_tree_dock()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position)); + SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_node_created)); + SceneTreeDock::get_singleton()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position)); + + EditorNode::get_singleton()->call_deferred(SNAME("connect"), "play_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(true)); + EditorNode::get_singleton()->call_deferred(SNAME("connect"), "stop_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(false)); - editor->call_deferred(SNAME("connect"), "play_pressed", Callable(this, "_update_override_camera_button"), make_binds(true)); - editor->call_deferred(SNAME("connect"), "stop_pressed", Callable(this, "_update_override_camera_button"), make_binds(false)); + // A fluid container for all toolbars. + HFlowContainer *main_flow = memnew(HFlowContainer); + add_child(main_flow); - hb = memnew(HBoxContainer); - add_child(hb); - hb->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + // Main toolbars. + HBoxContainer *main_menu_hbox = memnew(HBoxContainer); + main_menu_hbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + main_flow->add_child(main_menu_hbox); bottom_split = memnew(VSplitContainer); add_child(bottom_split); bottom_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); - palette_split = memnew(HSplitContainer); - bottom_split->add_child(palette_split); - palette_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); + left_panel_split = memnew(HSplitContainer); + bottom_split->add_child(left_panel_split); + left_panel_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + right_panel_split = memnew(HSplitContainer); + left_panel_split->add_child(right_panel_split); + right_panel_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); viewport_scrollable = memnew(Control); - palette_split->add_child(viewport_scrollable); + right_panel_split->add_child(viewport_scrollable); viewport_scrollable->set_mouse_filter(MOUSE_FILTER_PASS); viewport_scrollable->set_clip_contents(true); viewport_scrollable->set_v_size_flags(Control::SIZE_EXPAND_FILL); @@ -5227,20 +4982,62 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { SubViewportContainer *scene_tree = memnew(SubViewportContainer); viewport_scrollable->add_child(scene_tree); scene_tree->set_stretch(true); - scene_tree->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - scene_tree->add_child(p_editor->get_scene_root()); + scene_tree->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + scene_tree->add_child(EditorNode::get_singleton()->get_scene_root()); controls_vb = memnew(VBoxContainer); controls_vb->set_begin(Point2(5, 5)); - viewport = memnew(CanvasItemEditorViewport(p_editor, this)); + // To ensure that scripts can parse the list of shortcuts correctly, we have to define + // those shortcuts one by one. Define shortcut before using it (by EditorZoomWidget). + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_3.125_percent", TTR("Zoom to 3.125%"), + { int32_t(KeyModifierMask::SHIFT | Key::KEY_5), int32_t(KeyModifierMask::SHIFT | Key::KP_5) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_6.25_percent", TTR("Zoom to 6.25%"), + { int32_t(KeyModifierMask::SHIFT | Key::KEY_4), int32_t(KeyModifierMask::SHIFT | Key::KP_4) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_12.5_percent", TTR("Zoom to 12.5%"), + { int32_t(KeyModifierMask::SHIFT | Key::KEY_3), int32_t(KeyModifierMask::SHIFT | Key::KP_3) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_25_percent", TTR("Zoom to 25%"), + { int32_t(KeyModifierMask::SHIFT | Key::KEY_2), int32_t(KeyModifierMask::SHIFT | Key::KP_2) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_50_percent", TTR("Zoom to 50%"), + { int32_t(KeyModifierMask::SHIFT | Key::KEY_1), int32_t(KeyModifierMask::SHIFT | Key::KP_1) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_100_percent", TTR("Zoom to 100%"), + { int32_t(Key::KEY_1), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KEY_0), int32_t(Key::KP_1), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_0) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_200_percent", TTR("Zoom to 200%"), + { int32_t(Key::KEY_2), int32_t(Key::KP_2) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_400_percent", TTR("Zoom to 400%"), + { int32_t(Key::KEY_3), int32_t(Key::KP_3) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_800_percent", TTR("Zoom to 800%"), + { int32_t(Key::KEY_4), int32_t(Key::KP_4) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_1600_percent", TTR("Zoom to 1600%"), + { int32_t(Key::KEY_5), int32_t(Key::KP_5) }); + + zoom_widget = memnew(EditorZoomWidget); + controls_vb->add_child(zoom_widget); + zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE); + zoom_widget->set_shortcut_context(this); + zoom_widget->connect("zoom_changed", callable_mp(this, &CanvasItemEditor::_update_zoom)); + + panner.instantiate(); + panner->set_callbacks(callable_mp(this, &CanvasItemEditor::_pan_callback), callable_mp(this, &CanvasItemEditor::_zoom_callback)); + + viewport = memnew(CanvasItemEditorViewport(this)); viewport_scrollable->add_child(viewport); viewport->set_mouse_filter(MOUSE_FILTER_PASS); - viewport->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + viewport->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); viewport->set_clip_contents(true); viewport->set_focus_mode(FOCUS_ALL); viewport->connect("draw", callable_mp(this, &CanvasItemEditor::_draw_viewport)); viewport->connect("gui_input", callable_mp(this, &CanvasItemEditor::_gui_input_viewport)); + viewport->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key)); h_scroll = memnew(HScrollBar); viewport->add_child(h_scroll); @@ -5254,118 +5051,111 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { viewport->add_child(controls_vb); - zoom_widget = memnew(EditorZoomWidget); - controls_vb->add_child(zoom_widget); - zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE); - zoom_widget->connect("zoom_changed", callable_mp(this, &CanvasItemEditor::_update_zoom)); - - updating_scroll = false; - // Add some margin to the left for better aesthetics. // This prevents the first button's hover/pressed effect from "touching" the panel's border, // which looks ugly. Control *margin_left = memnew(Control); - hb->add_child(margin_left); + main_menu_hbox->add_child(margin_left); margin_left->set_custom_minimum_size(Size2(2, 0) * EDSCALE); select_button = memnew(Button); select_button->set_flat(true); - hb->add_child(select_button); + main_menu_hbox->add_child(select_button); select_button->set_toggle_mode(true); - select_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_SELECT)); + select_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_SELECT)); select_button->set_pressed(true); select_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/select_mode", TTR("Select Mode"), Key::Q)); select_button->set_shortcut_context(this); - select_button->set_tooltip(keycode_get_string((Key)KeyModifierMask::CMD) + TTR("Drag: Rotate selected node around pivot.") + "\n" + TTR("Alt+Drag: Move selected node.") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD) + TTR("Alt+Drag: Scale selected node.") + "\n" + TTR("V: Set selected node's pivot position.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD) + TTR("RMB: Add node at position clicked.")); + select_button->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Drag: Rotate selected node around pivot.") + "\n" + TTR("Alt+Drag: Move selected node.") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Alt+Drag: Scale selected node.") + "\n" + TTR("V: Set selected node's pivot position.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("RMB: Add node at position clicked.")); - hb->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); move_button = memnew(Button); move_button->set_flat(true); - hb->add_child(move_button); + main_menu_hbox->add_child(move_button); move_button->set_toggle_mode(true); - move_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_MOVE)); + move_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_MOVE)); move_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/move_mode", TTR("Move Mode"), Key::W)); move_button->set_shortcut_context(this); - move_button->set_tooltip(TTR("Move Mode")); + move_button->set_tooltip_text(TTR("Move Mode")); rotate_button = memnew(Button); rotate_button->set_flat(true); - hb->add_child(rotate_button); + main_menu_hbox->add_child(rotate_button); rotate_button->set_toggle_mode(true); - rotate_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_ROTATE)); + rotate_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_ROTATE)); rotate_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/rotate_mode", TTR("Rotate Mode"), Key::E)); rotate_button->set_shortcut_context(this); - rotate_button->set_tooltip(TTR("Rotate Mode")); + rotate_button->set_tooltip_text(TTR("Rotate Mode")); scale_button = memnew(Button); scale_button->set_flat(true); - hb->add_child(scale_button); + main_menu_hbox->add_child(scale_button); scale_button->set_toggle_mode(true); - scale_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_SCALE)); + scale_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_SCALE)); scale_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/scale_mode", TTR("Scale Mode"), Key::S)); scale_button->set_shortcut_context(this); - scale_button->set_tooltip(TTR("Shift: Scale proportionally.")); + scale_button->set_tooltip_text(TTR("Shift: Scale proportionally.")); - hb->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); list_select_button = memnew(Button); list_select_button->set_flat(true); - hb->add_child(list_select_button); + main_menu_hbox->add_child(list_select_button); list_select_button->set_toggle_mode(true); - list_select_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_LIST_SELECT)); - list_select_button->set_tooltip(TTR("Show list of selectable nodes at position clicked.")); + list_select_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_LIST_SELECT)); + list_select_button->set_tooltip_text(TTR("Show list of selectable nodes at position clicked.")); pivot_button = memnew(Button); pivot_button->set_flat(true); - hb->add_child(pivot_button); + main_menu_hbox->add_child(pivot_button); pivot_button->set_toggle_mode(true); - pivot_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_EDIT_PIVOT)); - pivot_button->set_tooltip(TTR("Click to change object's rotation pivot.")); + pivot_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_EDIT_PIVOT)); + pivot_button->set_tooltip_text(TTR("Click to change object's rotation pivot.")); pan_button = memnew(Button); pan_button->set_flat(true); - hb->add_child(pan_button); + main_menu_hbox->add_child(pan_button); pan_button->set_toggle_mode(true); - pan_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_PAN)); + pan_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_PAN)); pan_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/pan_mode", TTR("Pan Mode"), Key::G)); pan_button->set_shortcut_context(this); - pan_button->set_tooltip(TTR("You can also use Pan View shortcut (Space by default) to pan in any mode.")); + pan_button->set_tooltip_text(TTR("You can also use Pan View shortcut (Space by default) to pan in any mode.")); ruler_button = memnew(Button); ruler_button->set_flat(true); - hb->add_child(ruler_button); + main_menu_hbox->add_child(ruler_button); ruler_button->set_toggle_mode(true); - ruler_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_RULER)); + ruler_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_RULER)); ruler_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/ruler_mode", TTR("Ruler Mode"), Key::R)); ruler_button->set_shortcut_context(this); - ruler_button->set_tooltip(TTR("Ruler Mode")); + ruler_button->set_tooltip_text(TTR("Ruler Mode")); - hb->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); smart_snap_button = memnew(Button); smart_snap_button->set_flat(true); - hb->add_child(smart_snap_button); + main_menu_hbox->add_child(smart_snap_button); smart_snap_button->set_toggle_mode(true); smart_snap_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_smart_snap)); - smart_snap_button->set_tooltip(TTR("Toggle smart snapping.")); + smart_snap_button->set_tooltip_text(TTR("Toggle smart snapping.")); smart_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_smart_snap", TTR("Use Smart Snap"), KeyModifierMask::SHIFT | Key::S)); smart_snap_button->set_shortcut_context(this); grid_snap_button = memnew(Button); grid_snap_button->set_flat(true); - hb->add_child(grid_snap_button); + main_menu_hbox->add_child(grid_snap_button); grid_snap_button->set_toggle_mode(true); grid_snap_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_grid_snap)); - grid_snap_button->set_tooltip(TTR("Toggle grid snapping.")); + grid_snap_button->set_tooltip_text(TTR("Toggle grid snapping.")); grid_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_grid_snap", TTR("Use Grid Snap"), KeyModifierMask::SHIFT | Key::G)); grid_snap_button->set_shortcut_context(this); snap_config_menu = memnew(MenuButton); snap_config_menu->set_shortcut_context(this); - hb->add_child(snap_config_menu); + main_menu_hbox->add_child(snap_config_menu); snap_config_menu->set_h_size_flags(SIZE_SHRINK_END); - snap_config_menu->set_tooltip(TTR("Snapping Options")); + snap_config_menu->set_tooltip_text(TTR("Snapping Options")); snap_config_menu->set_switch_on_hover(true); PopupMenu *p = snap_config_menu->get_popup(); @@ -5392,78 +5182,91 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_other_nodes", TTR("Snap to Other Nodes")), SNAP_USE_OTHER_NODES); smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_guides", TTR("Snap to Guides")), SNAP_USE_GUIDES); - hb->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); lock_button = memnew(Button); lock_button->set_flat(true); - hb->add_child(lock_button); + main_menu_hbox->add_child(lock_button); - lock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(LOCK_SELECTED)); - lock_button->set_tooltip(TTR("Lock selected node, preventing selection and movement.")); + lock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(LOCK_SELECTED)); + lock_button->set_tooltip_text(TTR("Lock selected node, preventing selection and movement.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - lock_button->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KeyModifierMask::CMD | Key::L)); + lock_button->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::L)); unlock_button = memnew(Button); unlock_button->set_flat(true); - hb->add_child(unlock_button); - unlock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(UNLOCK_SELECTED)); - unlock_button->set_tooltip(TTR("Unlock selected node, allowing selection and movement.")); + main_menu_hbox->add_child(unlock_button); + unlock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNLOCK_SELECTED)); + unlock_button->set_tooltip_text(TTR("Unlock selected node, allowing selection and movement.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - unlock_button->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::L)); + unlock_button->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::L)); group_button = memnew(Button); group_button->set_flat(true); - hb->add_child(group_button); - group_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(GROUP_SELECTED)); - group_button->set_tooltip(TTR("Makes sure the object's children are not selectable.")); + main_menu_hbox->add_child(group_button); + group_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(GROUP_SELECTED)); + group_button->set_tooltip_text(TTR("Make selected node's children not selectable.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - group_button->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD | Key::G)); + group_button->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::G)); ungroup_button = memnew(Button); ungroup_button->set_flat(true); - hb->add_child(ungroup_button); - ungroup_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(UNGROUP_SELECTED)); - ungroup_button->set_tooltip(TTR("Restores the object's children's ability to be selected.")); + main_menu_hbox->add_child(ungroup_button); + ungroup_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNGROUP_SELECTED)); + ungroup_button->set_tooltip_text(TTR("Make selected node's children selectable.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - ungroup_button->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::G)); + ungroup_button->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G)); - hb->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); skeleton_menu = memnew(MenuButton); skeleton_menu->set_shortcut_context(this); - hb->add_child(skeleton_menu); - skeleton_menu->set_tooltip(TTR("Skeleton Options")); + main_menu_hbox->add_child(skeleton_menu); + skeleton_menu->set_tooltip_text(TTR("Skeleton Options")); skeleton_menu->set_switch_on_hover(true); p = skeleton_menu->get_popup(); p->set_hide_on_checkable_item_selection(false); p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_show_bones", TTR("Show Bones")), SKELETON_SHOW_BONES); p->add_separator(); - p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_make_bones", TTR("Make Bone2D Node(s) from Node(s)"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::B), SKELETON_MAKE_BONES); + p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_make_bones", TTR("Make Bone2D Node(s) from Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::B), SKELETON_MAKE_BONES); p->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); - hb->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); override_camera_button = memnew(Button); override_camera_button->set_flat(true); - hb->add_child(override_camera_button); + main_menu_hbox->add_child(override_camera_button); override_camera_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_override_camera)); override_camera_button->set_toggle_mode(true); override_camera_button->set_disabled(true); _update_override_camera_button(false); - hb->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); view_menu = memnew(MenuButton); - view_menu->set_shortcut_context(this); + // TRANSLATORS: Noun, name of the 2D/3D View menus. view_menu->set_text(TTR("View")); - hb->add_child(view_menu); - view_menu->get_popup()->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); view_menu->set_switch_on_hover(true); + view_menu->set_shortcut_context(this); + main_menu_hbox->add_child(view_menu); + view_menu->get_popup()->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); p = view_menu->get_popup(); p->set_hide_on_checkable_item_selection(false); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Always Show Grid"), Key::NUMBERSIGN), SHOW_GRID); + + grid_menu = memnew(PopupMenu); + grid_menu->connect("about_to_popup", callable_mp(this, &CanvasItemEditor::_prepare_grid_menu)); + grid_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_on_grid_menu_id_pressed)); + grid_menu->set_name("GridMenu"); + grid_menu->add_radio_check_item(TTR("Show"), GRID_VISIBILITY_SHOW); + grid_menu->add_radio_check_item(TTR("Show When Snapping"), GRID_VISIBILITY_SHOW_WHEN_SNAPPING); + grid_menu->add_radio_check_item(TTR("Hide"), GRID_VISIBILITY_HIDE); + grid_menu->add_separator(); + grid_menu->add_shortcut(ED_SHORTCUT("canvas_item_editor/toggle_grid", TTR("Toggle Grid"), KeyModifierMask::CMD_OR_CTRL | Key::APOSTROPHE)); + p->add_child(grid_menu); + p->add_submenu_item(TTR("Grid"), "GridMenu"); + p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show Helpers"), Key::H), SHOW_HELPERS); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers")), SHOW_RULERS); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), Key::Y), SHOW_GUIDES); @@ -5477,43 +5280,19 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { p->add_shortcut(ED_SHORTCUT("canvas_item_editor/frame_selection", TTR("Frame Selection"), KeyModifierMask::SHIFT | Key::F), VIEW_FRAME_TO_SELECTION); p->add_shortcut(ED_SHORTCUT("canvas_item_editor/clear_guides", TTR("Clear Guides")), CLEAR_GUIDES); p->add_separator(); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/preview_canvas_scale", TTR("Preview Canvas Scale"), KeyModifierMask::SHIFT | KeyModifierMask::CMD | Key::P), PREVIEW_CANVAS_SCALE); - - hb->add_child(memnew(VSeparator)); - - context_menu_container = memnew(PanelContainer); - hbc_context_menu = memnew(HBoxContainer); - context_menu_container->add_child(hbc_context_menu); - // Use a custom stylebox to make contextual menu items stand out from the rest. - // This helps with editor usability as contextual menu items change when selecting nodes, - // even though it may not be immediately obvious at first. - hb->add_child(context_menu_container); - _update_context_menu_stylebox(); - - presets_menu = memnew(MenuButton); - presets_menu->set_shortcut_context(this); - presets_menu->set_text(TTR("Layout")); - hbc_context_menu->add_child(presets_menu); - presets_menu->hide(); - presets_menu->set_switch_on_hover(true); - - p = presets_menu->get_popup(); - p->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); + p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/preview_canvas_scale", TTR("Preview Canvas Scale")), PREVIEW_CANVAS_SCALE); - anchors_popup = memnew(PopupMenu); - p->add_child(anchors_popup); - anchors_popup->set_name("Anchors"); - anchors_popup->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); + main_menu_hbox->add_child(memnew(VSeparator)); - anchor_mode_button = memnew(Button); - anchor_mode_button->set_flat(true); - hbc_context_menu->add_child(anchor_mode_button); - anchor_mode_button->set_toggle_mode(true); - anchor_mode_button->hide(); - anchor_mode_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_anchor_mode)); + // Contextual toolbars. + context_menu_panel = memnew(PanelContainer); + context_menu_hbox = memnew(HBoxContainer); + context_menu_panel->add_child(context_menu_hbox); + main_flow->add_child(context_menu_panel); + // Animation controls. animation_hb = memnew(HBoxContainer); - hbc_context_menu->add_child(animation_hb); + context_menu_hbox->add_child(animation_hb); animation_hb->add_child(memnew(VSeparator)); animation_hb->hide(); @@ -5522,8 +5301,8 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { key_loc_button->set_toggle_mode(true); key_loc_button->set_pressed(true); key_loc_button->set_focus_mode(FOCUS_NONE); - key_loc_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_POS)); - key_loc_button->set_tooltip(TTR("Translation mask for inserting keys.")); + key_loc_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_POS)); + key_loc_button->set_tooltip_text(TTR("Translation mask for inserting keys.")); animation_hb->add_child(key_loc_button); key_rot_button = memnew(Button); @@ -5531,23 +5310,23 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { key_rot_button->set_toggle_mode(true); key_rot_button->set_pressed(true); key_rot_button->set_focus_mode(FOCUS_NONE); - key_rot_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_ROT)); - key_rot_button->set_tooltip(TTR("Rotation mask for inserting keys.")); + key_rot_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_ROT)); + key_rot_button->set_tooltip_text(TTR("Rotation mask for inserting keys.")); animation_hb->add_child(key_rot_button); key_scale_button = memnew(Button); key_scale_button->set_flat(true); key_scale_button->set_toggle_mode(true); key_scale_button->set_focus_mode(FOCUS_NONE); - key_scale_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_SCALE)); - key_scale_button->set_tooltip(TTR("Scale mask for inserting keys.")); + key_scale_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_SCALE)); + key_scale_button->set_tooltip_text(TTR("Scale mask for inserting keys.")); animation_hb->add_child(key_scale_button); key_insert_button = memnew(Button); key_insert_button->set_flat(true); key_insert_button->set_focus_mode(FOCUS_NONE); - key_insert_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_KEY)); - key_insert_button->set_tooltip(TTR("Insert keys (based on mask).")); + key_insert_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_KEY)); + key_insert_button->set_tooltip_text(TTR("Insert keys (based on mask).")); key_insert_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/anim_insert_key", TTR("Insert Key"), Key::INSERT)); key_insert_button->set_shortcut_context(this); animation_hb->add_child(key_insert_button); @@ -5556,14 +5335,14 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { key_auto_insert_button->set_flat(true); key_auto_insert_button->set_toggle_mode(true); key_auto_insert_button->set_focus_mode(FOCUS_NONE); - key_auto_insert_button->set_tooltip(TTR("Auto insert keys when objects are translated, rotated or scaled (based on mask).\nKeys are only added to existing tracks, no new tracks will be created.\nKeys must be inserted manually for the first time.")); + key_auto_insert_button->set_tooltip_text(TTR("Auto insert keys when objects are translated, rotated or scaled (based on mask).\nKeys are only added to existing tracks, no new tracks will be created.\nKeys must be inserted manually for the first time.")); key_auto_insert_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/anim_auto_insert_key", TTR("Auto Insert Key"))); key_auto_insert_button->set_shortcut_context(this); animation_hb->add_child(key_auto_insert_button); animation_menu = memnew(MenuButton); animation_menu->set_shortcut_context(this); - animation_menu->set_tooltip(TTR("Animation Key and Pose Options")); + animation_menu->set_tooltip_text(TTR("Animation Key and Pose Options")); animation_hb->add_child(animation_menu); animation_menu->get_popup()->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); animation_menu->set_switch_on_hover(true); @@ -5571,7 +5350,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { p = animation_menu->get_popup(); p->add_shortcut(ED_GET_SHORTCUT("canvas_item_editor/anim_insert_key"), ANIM_INSERT_KEY); - p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_insert_key_existing_tracks", TTR("Insert Key (Existing Tracks)"), KeyModifierMask::CMD + Key::INSERT), ANIM_INSERT_KEY_EXISTING); + p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_insert_key_existing_tracks", TTR("Insert Key (Existing Tracks)"), KeyModifierMask::CMD_OR_CTRL + Key::INSERT), ANIM_INSERT_KEY_EXISTING); p->add_separator(); p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_copy_pose", TTR("Copy Pose")), ANIM_COPY_POSE); p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_paste_pose", TTR("Paste Pose")), ANIM_PASTE_POSE); @@ -5587,37 +5366,21 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { add_child(selection_menu); selection_menu->set_min_size(Vector2(100, 0)); selection_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_selection_result_pressed)); - selection_menu->connect("popup_hide", callable_mp(this, &CanvasItemEditor::_selection_menu_hide)); + selection_menu->connect("popup_hide", callable_mp(this, &CanvasItemEditor::_selection_menu_hide), CONNECT_DEFERRED); add_node_menu = memnew(PopupMenu); add_child(add_node_menu); - add_node_menu->add_icon_item(editor->get_scene_tree_dock()->get_theme_icon(SNAME("Add"), SNAME("EditorIcons")), TTR("Add Node Here")); - add_node_menu->add_icon_item(editor->get_scene_tree_dock()->get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), TTR("Instance Scene Here")); add_node_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_add_node_pressed)); multiply_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/multiply_grid_step", TTR("Multiply grid step by 2"), Key::KP_MULTIPLY); divide_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/divide_grid_step", TTR("Divide grid step by 2"), Key::KP_DIVIDE); - pan_view_shortcut = ED_SHORTCUT("canvas_item_editor/pan_view", TTR("Pan View"), Key::SPACE); skeleton_menu->get_popup()->set_item_checked(skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES), true); + + // Store the singleton instance. singleton = this; - // To ensure that scripts can parse the list of shortcuts correctly, we have to define - // those shortcuts one by one. - // Resetting zoom to 100% is a duplicate shortcut of `canvas_item_editor/reset_zoom`, - // but it ensures both 1 and Ctrl + 0 can be used to reset zoom. - ED_SHORTCUT("canvas_item_editor/zoom_3.125_percent", TTR("Zoom to 3.125%"), KeyModifierMask::SHIFT | Key::KEY_5); - ED_SHORTCUT("canvas_item_editor/zoom_6.25_percent", TTR("Zoom to 6.25%"), KeyModifierMask::SHIFT | Key::KEY_4); - ED_SHORTCUT("canvas_item_editor/zoom_12.5_percent", TTR("Zoom to 12.5%"), KeyModifierMask::SHIFT | Key::KEY_3); - ED_SHORTCUT("canvas_item_editor/zoom_25_percent", TTR("Zoom to 25%"), KeyModifierMask::SHIFT | Key::KEY_2); - ED_SHORTCUT("canvas_item_editor/zoom_50_percent", TTR("Zoom to 50%"), KeyModifierMask::SHIFT | Key::KEY_1); - ED_SHORTCUT("canvas_item_editor/zoom_100_percent", TTR("Zoom to 100%"), Key::KEY_1); - ED_SHORTCUT("canvas_item_editor/zoom_200_percent", TTR("Zoom to 200%"), Key::KEY_2); - ED_SHORTCUT("canvas_item_editor/zoom_400_percent", TTR("Zoom to 400%"), Key::KEY_3); - ED_SHORTCUT("canvas_item_editor/zoom_800_percent", TTR("Zoom to 800%"), Key::KEY_4); - ED_SHORTCUT("canvas_item_editor/zoom_1600_percent", TTR("Zoom to 1600%"), Key::KEY_5); - - set_process_unhandled_key_input(true); + set_process_shortcut_input(true); // Update the menus' checkboxes call_deferred(SNAME("set_state"), get_state()); @@ -5626,7 +5389,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { CanvasItemEditor *CanvasItemEditor::singleton = nullptr; void CanvasItemEditorPlugin::edit(Object *p_object) { - canvas_item_editor->set_undo_redo(&get_undo_redo()); canvas_item_editor->edit(Object::cast_to<CanvasItem>(p_object)); } @@ -5638,12 +5400,14 @@ void CanvasItemEditorPlugin::make_visible(bool p_visible) { if (p_visible) { canvas_item_editor->show(); canvas_item_editor->set_physics_process(true); - RenderingServer::get_singleton()->viewport_set_disable_2d(editor->get_scene_root()->get_viewport_rid(), false); + RenderingServer::get_singleton()->viewport_set_disable_2d(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), false); + RenderingServer::get_singleton()->viewport_set_environment_mode(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), RS::VIEWPORT_ENVIRONMENT_ENABLED); } else { canvas_item_editor->hide(); canvas_item_editor->set_physics_process(false); - RenderingServer::get_singleton()->viewport_set_disable_2d(editor->get_scene_root()->get_viewport_rid(), true); + RenderingServer::get_singleton()->viewport_set_disable_2d(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), true); + RenderingServer::get_singleton()->viewport_set_environment_mode(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), RS::VIEWPORT_ENVIRONMENT_DISABLED); } } @@ -5655,12 +5419,20 @@ void CanvasItemEditorPlugin::set_state(const Dictionary &p_state) { canvas_item_editor->set_state(p_state); } -CanvasItemEditorPlugin::CanvasItemEditorPlugin(EditorNode *p_node) { - editor = p_node; - canvas_item_editor = memnew(CanvasItemEditor(editor)); +void CanvasItemEditorPlugin::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + connect("scene_changed", callable_mp((CanvasItem *)canvas_item_editor->get_viewport_control(), &CanvasItem::queue_redraw).unbind(1)); + connect("scene_closed", callable_mp((CanvasItem *)canvas_item_editor->get_viewport_control(), &CanvasItem::queue_redraw).unbind(1)); + } break; + } +} + +CanvasItemEditorPlugin::CanvasItemEditorPlugin() { + canvas_item_editor = memnew(CanvasItemEditor); canvas_item_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); - editor->get_main_control()->add_child(canvas_item_editor); - canvas_item_editor->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + EditorNode::get_singleton()->get_main_screen_control()->add_child(canvas_item_editor); + canvas_item_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); canvas_item_editor->hide(); } @@ -5699,11 +5471,16 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons bool add_preview = false; for (int i = 0; i < files.size(); i++) { String path = files[i]; - RES res = ResourceLoader::load(path); + Ref<Resource> res = ResourceLoader::load(path); ERR_FAIL_COND(res.is_null()); Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(*res)); Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); if (texture != nullptr || scene != nullptr) { + bool root_node_selected = EditorNode::get_singleton()->get_editor_selection()->is_selected(EditorNode::get_singleton()->get_edited_scene()); + String desc = TTR("Drag and drop to add as child of current scene's root node.") + "\n" + TTR("Hold Ctrl when dropping to add as child of selected node."); + if (!root_node_selected) { + desc += "\n" + TTR("Hold Shift when dropping to add as sibling of selected node."); + } if (texture != nullptr) { Sprite2D *sprite = memnew(Sprite2D); sprite->set_texture(texture); @@ -5711,14 +5488,15 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons preview_node->add_child(sprite); label->show(); label_desc->show(); - label_desc->set_text(TTR("Drag and drop to add as child of current scene's root node.\nHold Ctrl when dropping to add as child of selected node.\nHold Shift when dropping to add as sibling of selected node.\nHold Alt when dropping to add as a different node type.")); + desc += "\n" + TTR("Hold Alt when dropping to add as a different node type."); + label_desc->set_text(desc); } else { if (scene.is_valid()) { Node *instance = scene->instantiate(); if (instance) { preview_node->add_child(instance); label_desc->show(); - label_desc->set_text(TTR("Drag and drop to add as child of current scene's root node.\nHold Ctrl when dropping to add as child of selected node.\nHold Shift when dropping to add as sibling of selected node.")); + label_desc->set_text(desc); } } } @@ -5727,7 +5505,7 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons } if (add_preview) { - editor->get_scene_root()->add_child(preview_node); + EditorNode::get_singleton()->get_scene_root()->add_child(preview_node); } } @@ -5735,10 +5513,10 @@ void CanvasItemEditorViewport::_remove_preview() { if (preview_node->get_parent()) { for (int i = preview_node->get_child_count() - 1; i >= 0; i--) { Node *node = preview_node->get_child(i); - node->queue_delete(); + node->queue_free(); preview_node->remove_child(node); } - editor->get_scene_root()->remove_child(preview_node); + EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node); label->hide(); label_desc->hide(); @@ -5763,73 +5541,63 @@ bool CanvasItemEditorViewport::_cyclical_dependency_exists(const String &p_targe void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &path, const Point2 &p_point) { // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. String name = path.get_file().get_basename(); - switch (ProjectSettings::get_singleton()->get("editor/node_naming/name_casing").operator int()) { - case NAME_CASING_PASCAL_CASE: - name = name.capitalize().replace(" ", ""); - break; - case NAME_CASING_CAMEL_CASE: - name = name.capitalize().replace(" ", ""); - name[0] = name.to_lower()[0]; - break; - case NAME_CASING_SNAKE_CASE: - name = name.capitalize().replace(" ", "_").to_lower(); - break; - } - child->set_name(name); + child->set_name(Node::adjust_name_casing(name)); - Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(ResourceCache::get(path))); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + Ref<Texture2D> texture = ResourceCache::get_ref(path); if (parent) { - editor_data->get_undo_redo().add_do_method(parent, "add_child", child, true); - editor_data->get_undo_redo().add_do_method(child, "set_owner", editor->get_edited_scene()); - editor_data->get_undo_redo().add_do_reference(child); - editor_data->get_undo_redo().add_undo_method(parent, "remove_child", child); + undo_redo->add_do_method(parent, "add_child", child, true); + undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(child); + undo_redo->add_undo_method(parent, "remove_child", child); } else { // If no parent is selected, set as root node of the scene. - editor_data->get_undo_redo().add_do_method(editor, "set_edited_scene", child); - editor_data->get_undo_redo().add_do_method(child, "set_owner", editor->get_edited_scene()); - editor_data->get_undo_redo().add_do_reference(child); - editor_data->get_undo_redo().add_undo_method(editor, "set_edited_scene", (Object *)nullptr); + undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child); + undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(child); + undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr); } if (parent) { String new_name = parent->validate_child_name(child); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); - editor_data->get_undo_redo().add_do_method(ed, "live_debug_create_node", editor->get_edited_scene()->get_path_to(parent), child->get_class(), new_name); - editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent), child->get_class(), new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); } - String node_class = child->get_class(); - if (node_class == "Polygon2D") { - editor_data->get_undo_redo().add_do_property(child, "texture/texture", texture); - } else if (node_class == "TouchScreenButton") { - editor_data->get_undo_redo().add_do_property(child, "normal", texture); - } else if (node_class == "TextureButton") { - editor_data->get_undo_redo().add_do_property(child, "texture_button", texture); + if (Object::cast_to<TouchScreenButton>(child) || Object::cast_to<TextureButton>(child)) { + undo_redo->add_do_property(child, "texture_normal", texture); } else { - editor_data->get_undo_redo().add_do_property(child, "texture", texture); + undo_redo->add_do_property(child, "texture", texture); } // make visible for certain node type - if (ClassDB::is_parent_class(node_class, "Control")) { + if (Object::cast_to<Control>(child)) { Size2 texture_size = texture->get_size(); - editor_data->get_undo_redo().add_do_property(child, "rect_size", texture_size); - } else if (node_class == "Polygon2D") { + undo_redo->add_do_property(child, "size", texture_size); + } else if (Object::cast_to<Polygon2D>(child)) { Size2 texture_size = texture->get_size(); - Vector<Vector2> list; - list.push_back(Vector2(0, 0)); - list.push_back(Vector2(texture_size.width, 0)); - list.push_back(Vector2(texture_size.width, texture_size.height)); - list.push_back(Vector2(0, texture_size.height)); - editor_data->get_undo_redo().add_do_property(child, "polygon", list); + Vector<Vector2> list = { + Vector2(0, 0), + Vector2(texture_size.width, 0), + Vector2(texture_size.width, texture_size.height), + Vector2(0, texture_size.height) + }; + undo_redo->add_do_property(child, "polygon", list); } // Compute the global position Transform2D xform = canvas_item_editor->get_canvas_transform(); Point2 target_position = xform.affine_inverse().xform(p_point); + // Adjust position for Control and TouchScreenButton + if (Object::cast_to<Control>(child) || Object::cast_to<TouchScreenButton>(child)) { + target_position -= texture->get_size() / 2; + } + // there's nothing to be used as source position so snapping will work as absolute if enabled target_position = canvas_item_editor->snap_point(target_position); - editor_data->get_undo_redo().add_do_method(child, "set_global_position", target_position); + undo_redo->add_do_method(child, "set_global_position", target_position); } bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) { @@ -5839,12 +5607,14 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons } Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); - if (!instantiated_scene) { // error on instancing + if (!instantiated_scene) { // Error on instantiation. return false; } - if (!editor->get_edited_scene()->get_scene_file_path().is_empty()) { // cyclical instancing - if (_cyclical_dependency_exists(editor->get_edited_scene()->get_scene_file_path(), instantiated_scene)) { + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + + if (!edited_scene->get_scene_file_path().is_empty()) { // Cyclic instantiation. + if (_cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) { memdelete(instantiated_scene); return false; } @@ -5852,27 +5622,30 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path)); - editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene); - editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", editor->get_edited_scene()); - editor_data->get_undo_redo().add_do_reference(instantiated_scene); - editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instantiated_scene); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->add_do_method(parent, "add_child", instantiated_scene, true); + undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene); + undo_redo->add_do_reference(instantiated_scene); + undo_redo->add_undo_method(parent, "remove_child", instantiated_scene); String new_name = parent->validate_child_name(instantiated_scene); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); - editor_data->get_undo_redo().add_do_method(ed, "live_debug_instance_node", editor->get_edited_scene()->get_path_to(parent), path, new_name); - editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(parent), path, new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)) + "/" + new_name)); - CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent); - if (parent_ci) { + CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene); + if (instance_ci) { Vector2 target_pos = canvas_item_editor->get_canvas_transform().affine_inverse().xform(p_point); target_pos = canvas_item_editor->snap_point(target_pos); - target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos); - // Preserve instance position of the original scene. - CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene); - if (instance_ci) { - target_pos += instance_ci->_edit_get_position(); + + CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent); + if (parent_ci) { + target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos); } - editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_position", target_pos); + // Preserve instance position of the original scene. + target_pos += instance_ci->_edit_get_position(); + + undo_redo->add_do_method(instantiated_scene, "set_position", target_pos); } return true; @@ -5890,11 +5663,12 @@ void CanvasItemEditorViewport::_perform_drop_data() { Vector<String> error_files; - editor_data->get_undo_redo().create_action(TTR("Create Node")); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Create Node")); for (int i = 0; i < selected_files.size(); i++) { String path = selected_files[i]; - RES res = ResourceLoader::load(path); + Ref<Resource> res = ResourceLoader::load(path); if (res.is_null()) { continue; } @@ -5921,7 +5695,7 @@ void CanvasItemEditorViewport::_perform_drop_data() { } } - editor_data->get_undo_redo().commit_action(); + undo_redo->commit_action(); if (error_files.size() > 0) { String files_str; @@ -5929,7 +5703,7 @@ void CanvasItemEditorViewport::_perform_drop_data() { files_str += error_files[i].get_file().get_basename() + ","; } files_str = files_str.substr(0, files_str.length() - 1); - accept->set_text(vformat(TTR("Error instancing scene from %s"), files_str.get_data())); + accept->set_text(vformat(TTR("Error instantiating scene from %s"), files_str.get_data())); accept->popup_centered(); } } @@ -5940,29 +5714,32 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian if (String(d["type"]) == "files") { Vector<String> files = d["files"]; bool can_instantiate = false; - for (int i = 0; i < files.size(); i++) { // check if dragged files contain resource or scene can be created at least once - RES res = ResourceLoader::load(files[i]); - if (res.is_null()) { - continue; - } - String type = res->get_class(); - if (type == "PackedScene") { - Ref<PackedScene> sdata = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); - Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); - if (!instantiated_scene) { + + List<String> scene_extensions; + ResourceLoader::get_recognized_extensions_for_type("PackedScene", &scene_extensions); + List<String> texture_extensions; + ResourceLoader::get_recognized_extensions_for_type("Texture2D", &texture_extensions); + + for (int i = 0; i < files.size(); i++) { + String extension = files[i].get_extension().to_lower(); + + // Check if dragged files with texture or scene extension can be created at least once. + if (texture_extensions.find(extension) || scene_extensions.find(extension)) { + Ref<Resource> res = ResourceLoader::load(files[i]); + if (res.is_null()) { continue; } - memdelete(instantiated_scene); - } else if (ClassDB::is_parent_class(type, "Texture2D")) { - Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(*res)); - if (!texture.is_valid()) { - continue; + Ref<PackedScene> scn = res; + if (scn.is_valid()) { + Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + if (!instantiated_scene) { + continue; + } + memdelete(instantiated_scene); } - } else { - continue; + can_instantiate = true; + break; } - can_instantiate = true; - break; } if (can_instantiate) { if (!preview_node->get_parent()) { // create preview only once @@ -6016,8 +5793,8 @@ void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p return; } - List<Node *> selected_nodes = editor->get_editor_selection()->get_selected_node_list(); - Node *root_node = editor->get_edited_scene(); + List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list(); + Node *root_node = EditorNode::get_singleton()->get_edited_scene(); if (selected_nodes.size() > 0) { Node *selected_node = selected_nodes[0]; target_node = root_node; @@ -6030,7 +5807,6 @@ void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p if (root_node) { target_node = root_node; } else { - drop_pos = p_point; target_node = nullptr; } } @@ -6068,25 +5844,36 @@ Node *CanvasItemEditorViewport::_make_texture_node_type(String texture_node_type return node; } +void CanvasItemEditorViewport::_update_theme() { + List<BaseButton *> btn_list; + button_group->get_buttons(&btn_list); + + for (int i = 0; i < btn_list.size(); i++) { + CheckBox *check = Object::cast_to<CheckBox>(btn_list[i]); + check->set_icon(get_theme_icon(check->get_text(), SNAME("EditorIcons"))); + } + + label->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); +} + void CanvasItemEditorViewport::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + _update_theme(); + } break; + case NOTIFICATION_ENTER_TREE: { + _update_theme(); connect("mouse_exited", callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit)); - label->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); } break; + case NOTIFICATION_EXIT_TREE: { disconnect("mouse_exited", callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit)); } break; - - default: - break; } } -void CanvasItemEditorViewport::_bind_methods() { -} - -CanvasItemEditorViewport::CanvasItemEditorViewport(EditorNode *p_node, CanvasItemEditor *p_canvas_item_editor) { +CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_item_editor) { default_texture_node_type = "Sprite2D"; // Node2D texture_node_types.push_back("Sprite2D"); @@ -6101,19 +5888,17 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(EditorNode *p_node, CanvasIte texture_node_types.push_back("NinePatchRect"); target_node = nullptr; - editor = p_node; - editor_data = editor->get_scene_tree_dock()->get_editor_data(); canvas_item_editor = p_canvas_item_editor; preview_node = memnew(Control); accept = memnew(AcceptDialog); - editor->get_gui_base()->add_child(accept); + EditorNode::get_singleton()->get_gui_base()->add_child(accept); selector = memnew(AcceptDialog); - editor->get_gui_base()->add_child(selector); + EditorNode::get_singleton()->get_gui_base()->add_child(selector); selector->set_title(TTR("Change Default Type")); selector->connect("confirmed", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed)); - selector->connect("cancelled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed)); + selector->connect("canceled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed)); VBoxContainer *vbc = memnew(VBoxContainer); selector->add_child(vbc); @@ -6123,14 +5908,14 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(EditorNode *p_node, CanvasIte btn_group = memnew(VBoxContainer); vbc->add_child(btn_group); - btn_group->set_h_size_flags(0); + btn_group->set_h_size_flags(SIZE_EXPAND_FILL); button_group.instantiate(); for (int i = 0; i < texture_node_types.size(); i++) { CheckBox *check = memnew(CheckBox); btn_group->add_child(check); check->set_text(texture_node_types[i]); - check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_type), varray(check)); + check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_type).bind(check)); check->set_button_group(button_group); } diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 8bba5130d4..ebe87a56f7 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -1,47 +1,52 @@ -/*************************************************************************/ -/* canvas_item_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 CONTROL_EDITOR_PLUGIN_H -#define CONTROL_EDITOR_PLUGIN_H - -#include "editor/editor_node.h" +/**************************************************************************/ +/* canvas_item_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 CANVAS_ITEM_EDITOR_PLUGIN_H +#define CANVAS_ITEM_EDITOR_PLUGIN_H + #include "editor/editor_plugin.h" -#include "editor/editor_zoom_widget.h" +#include "scene/gui/base_button.h" #include "scene/gui/box_container.h" -#include "scene/gui/check_box.h" -#include "scene/gui/label.h" -#include "scene/gui/panel_container.h" -#include "scene/gui/spin_box.h" -#include "scene/main/canvas_item.h" +class AcceptDialog; class CanvasItemEditorViewport; +class ConfirmationDialog; +class EditorData; +class EditorZoomWidget; +class HScrollBar; +class HSplitContainer; +class MenuButton; +class PanelContainer; +class ViewPanner; +class VScrollBar; +class VSplitContainer; class CanvasItemEditorSelectedItem : public Object { GDCLASS(CanvasItemEditorSelectedItem, Object); @@ -83,11 +88,11 @@ public: enum AddNodeOption { ADD_NODE, ADD_INSTANCE, + ADD_PASTE, + ADD_MOVE, }; private: - EditorNode *editor; - enum SnapTarget { SNAP_TARGET_NONE = 0, SNAP_TARGET_PARENT, @@ -113,7 +118,6 @@ private: SNAP_RELATIVE, SNAP_CONFIGURE, SNAP_USE_PIXEL, - SHOW_GRID, SHOW_HELPERS, SHOW_RULERS, SHOW_GUIDES, @@ -125,55 +129,6 @@ private: UNLOCK_SELECTED, GROUP_SELECTED, UNGROUP_SELECTED, - ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT, - ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP, - ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM, - ANCHORS_AND_OFFSETS_PRESET_CENTER, - ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE, - ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE, - ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE, - ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE, - ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE, - ANCHORS_AND_OFFSETS_PRESET_WIDE, - ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO, - ANCHORS_PRESET_TOP_LEFT, - ANCHORS_PRESET_TOP_RIGHT, - ANCHORS_PRESET_BOTTOM_LEFT, - ANCHORS_PRESET_BOTTOM_RIGHT, - ANCHORS_PRESET_CENTER_LEFT, - ANCHORS_PRESET_CENTER_RIGHT, - ANCHORS_PRESET_CENTER_TOP, - ANCHORS_PRESET_CENTER_BOTTOM, - ANCHORS_PRESET_CENTER, - ANCHORS_PRESET_TOP_WIDE, - ANCHORS_PRESET_LEFT_WIDE, - ANCHORS_PRESET_RIGHT_WIDE, - ANCHORS_PRESET_BOTTOM_WIDE, - ANCHORS_PRESET_VCENTER_WIDE, - ANCHORS_PRESET_HCENTER_WIDE, - ANCHORS_PRESET_WIDE, - OFFSETS_PRESET_TOP_LEFT, - OFFSETS_PRESET_TOP_RIGHT, - OFFSETS_PRESET_BOTTOM_LEFT, - OFFSETS_PRESET_BOTTOM_RIGHT, - OFFSETS_PRESET_CENTER_LEFT, - OFFSETS_PRESET_CENTER_RIGHT, - OFFSETS_PRESET_CENTER_TOP, - OFFSETS_PRESET_CENTER_BOTTOM, - OFFSETS_PRESET_CENTER, - OFFSETS_PRESET_TOP_WIDE, - OFFSETS_PRESET_LEFT_WIDE, - OFFSETS_PRESET_RIGHT_WIDE, - OFFSETS_PRESET_BOTTOM_WIDE, - OFFSETS_PRESET_VCENTER_WIDE, - OFFSETS_PRESET_HCENTER_WIDE, - OFFSETS_PRESET_WIDE, ANIM_INSERT_KEY, ANIM_INSERT_KEY_EXISTING, ANIM_INSERT_POS, @@ -221,65 +176,72 @@ private: DRAG_KEY_MOVE }; - bool selection_menu_additive_selection; + enum GridVisibility { + GRID_VISIBILITY_SHOW, + GRID_VISIBILITY_SHOW_WHEN_SNAPPING, + GRID_VISIBILITY_HIDE, + }; - Tool tool; - Control *viewport; - Control *viewport_scrollable; + bool selection_menu_additive_selection = false; - HScrollBar *h_scroll; - VScrollBar *v_scroll; - HBoxContainer *hb; + Tool tool = TOOL_SELECT; + Control *viewport = nullptr; + Control *viewport_scrollable = nullptr; + + HScrollBar *h_scroll = nullptr; + VScrollBar *v_scroll = nullptr; // Used for secondary menu items which are displayed depending on the currently selected node // (such as MeshInstance's "Mesh" menu). - PanelContainer *context_menu_container; - HBoxContainer *hbc_context_menu; + PanelContainer *context_menu_panel = nullptr; + HBoxContainer *context_menu_hbox = nullptr; Transform2D transform; - bool show_grid; - bool show_rulers; - bool show_guides; - bool show_origin; - bool show_viewport; - bool show_helpers; - bool show_edit_locks; - bool show_transformation_gizmos; - - real_t zoom; + GridVisibility grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING; + bool show_rulers = true; + bool show_guides = true; + bool show_origin = true; + bool show_viewport = true; + bool show_helpers = false; + bool show_edit_locks = true; + bool show_transformation_gizmos = true; + + real_t zoom = 1.0; Point2 view_offset; Point2 previous_update_view_offset; - bool selected_from_canvas; - bool anchors_mode; + bool selected_from_canvas = false; Point2 grid_offset; - Point2 grid_step; - int primary_grid_steps; - int grid_step_multiplier; - - real_t snap_rotation_step; - real_t snap_rotation_offset; - real_t snap_scale_step; - bool smart_snap_active; - bool grid_snap_active; - - bool snap_node_parent; - bool snap_node_anchors; - bool snap_node_sides; - bool snap_node_center; - bool snap_other_nodes; - bool snap_guides; - bool snap_rotation; - bool snap_scale; - bool snap_relative; - bool snap_pixel; - bool key_pos; - bool key_rot; - bool key_scale; - bool panning; - bool pan_pressed; - - bool ruler_tool_active; + Point2 grid_step = Point2(8, 8); // A power-of-two value works better as a default. + int primary_grid_steps = 8; + int grid_step_multiplier = 0; + + real_t snap_rotation_step = Math::deg_to_rad(15.0); + real_t snap_rotation_offset = 0.0; + real_t snap_scale_step = 0.1f; + bool smart_snap_active = false; + bool grid_snap_active = false; + + bool snap_node_parent = true; + bool snap_node_anchors = true; + bool snap_node_sides = true; + bool snap_node_center = true; + bool snap_other_nodes = true; + bool snap_guides = true; + bool snap_rotation = false; + bool snap_scale = false; + bool snap_relative = false; + // Enable pixel snapping even if pixel snap rendering is disabled in the Project Settings. + // This results in crisper visuals by preventing 2D nodes from being placed at subpixel coordinates. + bool snap_pixel = true; + + bool key_pos = true; + bool key_rot = true; + bool key_scale = false; + + bool pan_pressed = false; + + bool ruler_tool_active = false; Point2 ruler_tool_origin; Point2 node_create_position; @@ -308,7 +270,7 @@ private: uint64_t last_pass = 0; }; - uint64_t bone_last_frame; + uint64_t bone_last_frame = 0; struct BoneKey { ObjectID from; @@ -322,7 +284,7 @@ private: } }; - Map<BoneKey, BoneList> bone_list; + HashMap<BoneKey, BoneList> bone_list; struct PoseClipboard { Vector2 pos; @@ -332,65 +294,61 @@ private: }; List<PoseClipboard> pose_clipboard; - Button *select_button; - - Button *move_button; - Button *scale_button; - Button *rotate_button; + Button *select_button = nullptr; - Button *list_select_button; - Button *pivot_button; - Button *pan_button; + Button *move_button = nullptr; + Button *scale_button = nullptr; + Button *rotate_button = nullptr; - Button *ruler_button; + Button *list_select_button = nullptr; + Button *pivot_button = nullptr; + Button *pan_button = nullptr; - Button *smart_snap_button; - Button *grid_snap_button; - MenuButton *snap_config_menu; - PopupMenu *smartsnap_config_popup; + Button *ruler_button = nullptr; - Button *lock_button; - Button *unlock_button; + Button *smart_snap_button = nullptr; + Button *grid_snap_button = nullptr; + MenuButton *snap_config_menu = nullptr; + PopupMenu *smartsnap_config_popup = nullptr; - Button *group_button; - Button *ungroup_button; + Button *lock_button = nullptr; + Button *unlock_button = nullptr; - MenuButton *skeleton_menu; - Button *override_camera_button; - MenuButton *view_menu; - HBoxContainer *animation_hb; - MenuButton *animation_menu; + Button *group_button = nullptr; + Button *ungroup_button = nullptr; - MenuButton *presets_menu; - PopupMenu *anchors_and_margins_popup; - PopupMenu *anchors_popup; + MenuButton *skeleton_menu = nullptr; + Button *override_camera_button = nullptr; + MenuButton *view_menu = nullptr; + PopupMenu *grid_menu = nullptr; + HBoxContainer *animation_hb = nullptr; + MenuButton *animation_menu = nullptr; - Button *anchor_mode_button; + Button *key_loc_button = nullptr; + Button *key_rot_button = nullptr; + Button *key_scale_button = nullptr; + Button *key_insert_button = nullptr; + Button *key_auto_insert_button = nullptr; - Button *key_loc_button; - Button *key_rot_button; - Button *key_scale_button; - Button *key_insert_button; - Button *key_auto_insert_button; + PopupMenu *selection_menu = nullptr; + PopupMenu *add_node_menu = nullptr; - PopupMenu *selection_menu; - PopupMenu *add_node_menu; - - Control *top_ruler; - Control *left_ruler; + Control *top_ruler = nullptr; + Control *left_ruler = nullptr; Point2 drag_start_origin; - DragType drag_type; + DragType drag_type = DRAG_NONE; Point2 drag_from; Point2 drag_to; Point2 drag_rotation_center; List<CanvasItem *> drag_selection; - int dragged_guide_index; + int dragged_guide_index = -1; Point2 dragged_guide_pos; - bool is_hovering_h_guide; - bool is_hovering_v_guide; + bool is_hovering_h_guide = false; + bool is_hovering_v_guide = false; - bool updating_value_dialog; + bool updating_value_dialog = false; + Transform2D original_transform; Point2 box_selecting_to; @@ -402,9 +360,13 @@ private: Ref<Shortcut> set_pivot_shortcut; Ref<Shortcut> multiply_grid_step_shortcut; Ref<Shortcut> divide_grid_step_shortcut; - Ref<Shortcut> pan_view_shortcut; - bool _is_node_locked(const Node *p_node); + Ref<ViewPanner> panner; + bool warped_panning = true; + void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event); + void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event); + + bool _is_node_locked(const Node *p_node) const; bool _is_node_movable(const Node *p_node, bool p_popup_warning = false); void _find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<_SelectResult> &r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); void _get_canvas_items_at_pos(const Point2 &p_pos, Vector<_SelectResult> &r_items, bool p_allow_locked = false); @@ -412,9 +374,9 @@ private: void _find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_node, List<CanvasItem *> *r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); bool _select_click_on_item(CanvasItem *item, Point2 p_click_pos, bool p_append); - ConfirmationDialog *snap_dialog; + ConfirmationDialog *snap_dialog = nullptr; - CanvasItem *ref_item; + CanvasItem *ref_item = nullptr; void _save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones = false); void _restore_canvas_item_state(List<CanvasItem *> p_canvas_items, bool restore_bones = false); @@ -424,7 +386,7 @@ private: Vector2 _position_to_anchor(const Control *p_control, Vector2 position); void _popup_callback(int p_op); - bool updating_scroll; + bool updating_scroll = false; void _update_scroll(real_t); void _update_scrollbars(); void _snap_changed(); @@ -433,10 +395,12 @@ private: void _add_node_pressed(int p_result); void _node_created(Node *p_node); void _reset_create_position(); + void _update_editor_settings(); + bool _is_grid_visible() const; + void _prepare_grid_menu(); + void _on_grid_menu_id_pressed(int p_id); - UndoRedo *undo_redo; - - List<CanvasItem *> _get_edited_canvas_items(bool retreive_locked = false, bool remove_canvas_item_if_parent_in_selection = true); + List<CanvasItem *> _get_edited_canvas_items(bool retrieve_locked = false, bool remove_canvas_item_if_parent_in_selection = true) const; Rect2 _get_encompassing_rect_from_list(List<CanvasItem *> p_list); void _expand_encompassing_rect_using_children(Rect2 &r_rect, const Node *p_node, bool &r_first, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D(), bool include_locked_nodes = true); Rect2 _get_encompassing_rect(const Node *p_node); @@ -447,7 +411,7 @@ private: void _keying_changed(); - virtual void unhandled_key_input(const Ref<InputEvent> &p_ev) override; + virtual void shortcut_input(const Ref<InputEvent> &p_ev) override; void _draw_text_at_position(Point2 p_position, String p_string, Side p_side); void _draw_margin_at_position(int p_value, Point2 p_position, Side p_side); @@ -467,6 +431,7 @@ private: void _draw_invisible_nodes_positions(Node *p_node, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); void _draw_locks_and_groups(Node *p_node, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); void _draw_hover(); + void _draw_transform_message(); void _draw_viewport(); @@ -487,8 +452,8 @@ private: void _update_cursor(); void _selection_changed(); - void _focus_selection(int p_op); + void _reset_drag(); SnapTarget snap_target[2]; Transform2D snap_transform; @@ -510,15 +475,10 @@ private: const SnapTarget p_snap_target, List<const CanvasItem *> p_exceptions, const Node *p_current); - void _set_anchors_preset(Control::LayoutPreset p_preset); - void _set_anchors_and_offsets_preset(Control::LayoutPreset p_preset); - void _set_anchors_and_offsets_to_keep_ratio(); - - void _button_toggle_anchor_mode(bool p_status); - - VBoxContainer *controls_vb; - EditorZoomWidget *zoom_widget; + VBoxContainer *controls_vb = nullptr; + EditorZoomWidget *zoom_widget = nullptr; void _update_zoom(real_t p_zoom); + void _shortcut_zoom_set(real_t p_zoom); void _zoom_on_position(real_t p_zoom, Point2 p_position = Point2()); void _button_toggle_smart_snap(bool p_status); void _button_toggle_grid_snap(bool p_status); @@ -527,10 +487,9 @@ private: void _update_override_camera_button(bool p_game_running); - HSplitContainer *palette_split; - VSplitContainer *bottom_split; - - void _update_context_menu_stylebox(); + HSplitContainer *left_panel_split = nullptr; + HSplitContainer *right_panel_split = nullptr; + VSplitContainer *bottom_split = nullptr; void _set_owner_for_node_and_children(Node *p_node, Node *p_owner); @@ -541,8 +500,6 @@ protected: static void _bind_methods(); - HBoxContainer *get_panel_hb() { return hb; } - static CanvasItemEditor *singleton; public: @@ -571,7 +528,12 @@ public: void add_control_to_menu_panel(Control *p_control); void remove_control_from_menu_panel(Control *p_control); - HSplitContainer *get_palette_split(); + void add_control_to_left_panel(Control *p_control); + void remove_control_from_left_panel(Control *p_control); + + void add_control_to_right_panel(Control *p_control); + void remove_control_from_right_panel(Control *p_control); + VSplitContainer *get_bottom_split(); Control *get_viewport_control() { return viewport; } @@ -583,23 +545,25 @@ public: Tool get_current_tool() { return tool; } void set_current_tool(Tool p_tool); - void set_undo_redo(UndoRedo *p_undo_redo) { undo_redo = p_undo_redo; } void edit(CanvasItem *p_canvas_item); void focus_selection(); + void center_at(const Point2 &p_pos); - bool is_anchors_mode_enabled() { return anchors_mode; }; + virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override; - EditorSelection *editor_selection; + EditorSelection *editor_selection = nullptr; - CanvasItemEditor(EditorNode *p_editor); + CanvasItemEditor(); }; class CanvasItemEditorPlugin : public EditorPlugin { GDCLASS(CanvasItemEditorPlugin, EditorPlugin); - CanvasItemEditor *canvas_item_editor; - EditorNode *editor; + CanvasItemEditor *canvas_item_editor = nullptr; + +protected: + void _notification(int p_what); public: virtual String get_name() const override { return "2D"; } @@ -612,7 +576,7 @@ public: CanvasItemEditor *get_canvas_item_editor() { return canvas_item_editor; } - CanvasItemEditorPlugin(EditorNode *p_node); + CanvasItemEditorPlugin(); ~CanvasItemEditorPlugin(); }; @@ -625,19 +589,17 @@ class CanvasItemEditorViewport : public Control { Vector<String> texture_node_types; Vector<String> selected_files; - Node *target_node; + Node *target_node = nullptr; Point2 drop_pos; - EditorNode *editor; - EditorData *editor_data; - CanvasItemEditor *canvas_item_editor; - Control *preview_node; - AcceptDialog *accept; - AcceptDialog *selector; - Label *selector_label; - Label *label; - Label *label_desc; - VBoxContainer *btn_group; + CanvasItemEditor *canvas_item_editor = nullptr; + Control *preview_node = nullptr; + AcceptDialog *accept = nullptr; + AcceptDialog *selector = nullptr; + Label *selector_label = nullptr; + Label *label = nullptr; + Label *label_desc = nullptr; + VBoxContainer *btn_group = nullptr; Ref<ButtonGroup> button_group; void _on_mouse_exit(); @@ -655,8 +617,7 @@ class CanvasItemEditorViewport : public Control { bool _create_instance(Node *parent, String &path, const Point2 &p_point); void _perform_drop_data(); void _show_resource_type_selector(); - - static void _bind_methods(); + void _update_theme(); protected: void _notification(int p_what); @@ -665,8 +626,8 @@ public: virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override; virtual void drop_data(const Point2 &p_point, const Variant &p_data) override; - CanvasItemEditorViewport(EditorNode *p_node, CanvasItemEditor *p_canvas_item_editor); + CanvasItemEditorViewport(CanvasItemEditor *p_canvas_item_editor); ~CanvasItemEditorViewport(); }; -#endif +#endif // CANVAS_ITEM_EDITOR_PLUGIN_H diff --git a/editor/plugins/cast_2d_editor_plugin.cpp b/editor/plugins/cast_2d_editor_plugin.cpp new file mode 100644 index 0000000000..723082c293 --- /dev/null +++ b/editor/plugins/cast_2d_editor_plugin.cpp @@ -0,0 +1,153 @@ +/**************************************************************************/ +/* cast_2d_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 "cast_2d_editor_plugin.h" + +#include "canvas_item_editor_plugin.h" +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" +#include "scene/2d/ray_cast_2d.h" +#include "scene/2d/shape_cast_2d.h" + +void Cast2DEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", callable_mp(this, &Cast2DEditor::_node_removed)); + } break; + + case NOTIFICATION_EXIT_TREE: { + get_tree()->disconnect("node_removed", callable_mp(this, &Cast2DEditor::_node_removed)); + } break; + } +} + +void Cast2DEditor::_node_removed(Node *p_node) { + if (p_node == node) { + node = nullptr; + } +} + +bool Cast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { + if (!node || !node->is_visible_in_tree()) { + return false; + } + + Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { + Vector2 target_position = node->get("target_position"); + + if (mb->is_pressed()) { + if (xform.xform(target_position).distance_to(mb->get_position()) < 8) { + pressed = true; + original_target_position = target_position; + + return true; + } else { + pressed = false; + + return false; + } + } else if (pressed) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Set target_position")); + undo_redo->add_do_property(node, "target_position", target_position); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_property(node, "target_position", original_target_position); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + undo_redo->commit_action(); + + pressed = false; + + return true; + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid() && pressed) { + Vector2 point = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mm->get_position())); + point = node->get_global_transform().affine_inverse().xform(point); + + node->set("target_position", point); + canvas_item_editor->update_viewport(); + node->notify_property_list_changed(); + + return true; + } + + return false; +} + +void Cast2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { + if (!node || !node->is_visible_in_tree()) { + return; + } + + Transform2D gt = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + + const Ref<Texture2D> handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); + p_overlay->draw_texture(handle, gt.xform((Vector2)node->get("target_position")) - handle->get_size() / 2); +} + +void Cast2DEditor::edit(Node2D *p_node) { + if (!canvas_item_editor) { + canvas_item_editor = CanvasItemEditor::get_singleton(); + } + + if (Object::cast_to<RayCast2D>(p_node) || Object::cast_to<ShapeCast2D>(p_node)) { + node = p_node; + } else { + node = nullptr; + } + + canvas_item_editor->update_viewport(); +} + +/////////////////////// + +void Cast2DEditorPlugin::edit(Object *p_object) { + cast_2d_editor->edit(Object::cast_to<Node2D>(p_object)); +} + +bool Cast2DEditorPlugin::handles(Object *p_object) const { + return Object::cast_to<RayCast2D>(p_object) != nullptr || Object::cast_to<ShapeCast2D>(p_object) != nullptr; +} + +void Cast2DEditorPlugin::make_visible(bool p_visible) { + if (!p_visible) { + edit(nullptr); + } +} + +Cast2DEditorPlugin::Cast2DEditorPlugin() { + cast_2d_editor = memnew(Cast2DEditor); + EditorNode::get_singleton()->get_gui_base()->add_child(cast_2d_editor); +} diff --git a/editor/plugins/cast_2d_editor_plugin.h b/editor/plugins/cast_2d_editor_plugin.h new file mode 100644 index 0000000000..ad48d6a524 --- /dev/null +++ b/editor/plugins/cast_2d_editor_plugin.h @@ -0,0 +1,76 @@ +/**************************************************************************/ +/* cast_2d_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 CAST_2D_EDITOR_PLUGIN_H +#define CAST_2D_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "scene/2d/node_2d.h" + +class CanvasItemEditor; + +class Cast2DEditor : public Control { + GDCLASS(Cast2DEditor, Control); + + CanvasItemEditor *canvas_item_editor = nullptr; + Node2D *node = nullptr; + + bool pressed = false; + Point2 original_target_position; + +protected: + void _notification(int p_what); + void _node_removed(Node *p_node); + +public: + bool forward_canvas_gui_input(const Ref<InputEvent> &p_event); + void forward_canvas_draw_over_viewport(Control *p_overlay); + void edit(Node2D *p_node); +}; + +class Cast2DEditorPlugin : public EditorPlugin { + GDCLASS(Cast2DEditorPlugin, EditorPlugin); + + Cast2DEditor *cast_2d_editor = nullptr; + +public: + virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return cast_2d_editor->forward_canvas_gui_input(p_event); } + virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { cast_2d_editor->forward_canvas_draw_over_viewport(p_overlay); } + + virtual String get_name() const override { return "Cast2D"; } + bool has_main_screen() const override { return false; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool visible) override; + + Cast2DEditorPlugin(); +}; + +#endif // CAST_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/collision_polygon_2d_editor_plugin.cpp b/editor/plugins/collision_polygon_2d_editor_plugin.cpp index 22d3768a97..7f4e0d3f27 100644 --- a/editor/plugins/collision_polygon_2d_editor_plugin.cpp +++ b/editor/plugins/collision_polygon_2d_editor_plugin.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* collision_polygon_2d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* collision_polygon_2d_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 "collision_polygon_2d_editor_plugin.h" @@ -38,11 +38,8 @@ void CollisionPolygon2DEditor::_set_node(Node *p_polygon) { node = Object::cast_to<CollisionPolygon2D>(p_polygon); } -CollisionPolygon2DEditor::CollisionPolygon2DEditor(EditorNode *p_editor) : - AbstractPolygon2DEditor(p_editor) { - node = nullptr; -} +CollisionPolygon2DEditor::CollisionPolygon2DEditor() {} -CollisionPolygon2DEditorPlugin::CollisionPolygon2DEditorPlugin(EditorNode *p_node) : - AbstractPolygon2DEditorPlugin(p_node, memnew(CollisionPolygon2DEditor(p_node)), "CollisionPolygon2D") { +CollisionPolygon2DEditorPlugin::CollisionPolygon2DEditorPlugin() : + AbstractPolygon2DEditorPlugin(memnew(CollisionPolygon2DEditor), "CollisionPolygon2D") { } diff --git a/editor/plugins/collision_polygon_2d_editor_plugin.h b/editor/plugins/collision_polygon_2d_editor_plugin.h index cf2e452937..070a01f651 100644 --- a/editor/plugins/collision_polygon_2d_editor_plugin.h +++ b/editor/plugins/collision_polygon_2d_editor_plugin.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* collision_polygon_2d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* collision_polygon_2d_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 COLLISION_POLYGON_2D_EDITOR_PLUGIN_H #define COLLISION_POLYGON_2D_EDITOR_PLUGIN_H @@ -37,21 +37,21 @@ class CollisionPolygon2DEditor : public AbstractPolygon2DEditor { GDCLASS(CollisionPolygon2DEditor, AbstractPolygon2DEditor); - CollisionPolygon2D *node; + CollisionPolygon2D *node = nullptr; protected: virtual Node2D *_get_node() const override; virtual void _set_node(Node *p_polygon) override; public: - CollisionPolygon2DEditor(EditorNode *p_editor); + CollisionPolygon2DEditor(); }; class CollisionPolygon2DEditorPlugin : public AbstractPolygon2DEditorPlugin { GDCLASS(CollisionPolygon2DEditorPlugin, AbstractPolygon2DEditorPlugin); public: - CollisionPolygon2DEditorPlugin(EditorNode *p_node); + CollisionPolygon2DEditorPlugin(); }; #endif // COLLISION_POLYGON_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp index 8a5df6ac50..c2d5885e43 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.cpp +++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp @@ -1,37 +1,39 @@ -/*************************************************************************/ -/* collision_shape_2d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* collision_shape_2d_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 "collision_shape_2d_editor_plugin.h" #include "canvas_item_editor_plugin.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" #include "scene/resources/capsule_shape_2d.h" #include "scene/resources/circle_shape_2d.h" #include "scene/resources/concave_polygon_shape_2d.h" @@ -217,6 +219,7 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) { } void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Set Handle")); switch (shape_type) { @@ -323,6 +326,10 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e return false; } + if (!node->is_visible_in_tree()) { + return false; + } + if (shape_type == -1) { return false; } @@ -445,6 +452,10 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla return; } + if (!node->is_visible_in_tree()) { + return; + } + _get_current_shape_type(); if (shape_type == -1) { @@ -574,12 +585,9 @@ void CollisionShape2DEditor::_bind_methods() { ClassDB::bind_method("_get_current_shape_type", &CollisionShape2DEditor::_get_current_shape_type); } -CollisionShape2DEditor::CollisionShape2DEditor(EditorNode *p_editor) { +CollisionShape2DEditor::CollisionShape2DEditor() { node = nullptr; canvas_item_editor = nullptr; - editor = p_editor; - - undo_redo = p_editor->get_undo_redo(); edit_handle = -1; pressed = false; @@ -601,11 +609,9 @@ void CollisionShape2DEditorPlugin::make_visible(bool visible) { } } -CollisionShape2DEditorPlugin::CollisionShape2DEditorPlugin(EditorNode *p_editor) { - editor = p_editor; - - collision_shape_2d_editor = memnew(CollisionShape2DEditor(p_editor)); - p_editor->get_gui_base()->add_child(collision_shape_2d_editor); +CollisionShape2DEditorPlugin::CollisionShape2DEditorPlugin() { + collision_shape_2d_editor = memnew(CollisionShape2DEditor); + EditorNode::get_singleton()->get_gui_base()->add_child(collision_shape_2d_editor); } CollisionShape2DEditorPlugin::~CollisionShape2DEditorPlugin() { diff --git a/editor/plugins/collision_shape_2d_editor_plugin.h b/editor/plugins/collision_shape_2d_editor_plugin.h index 1c01b7019f..68fc0ddbdf 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.h +++ b/editor/plugins/collision_shape_2d_editor_plugin.h @@ -1,39 +1,37 @@ -/*************************************************************************/ -/* collision_shape_2d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* collision_shape_2d_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 COLLISION_SHAPE_2D_EDITOR_PLUGIN_H #define COLLISION_SHAPE_2D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" - #include "scene/2d/collision_shape_2d.h" class CanvasItemEditor; @@ -63,10 +61,8 @@ class CollisionShape2DEditor : public Control { Point2(1, -1), }; - EditorNode *editor; - UndoRedo *undo_redo; - CanvasItemEditor *canvas_item_editor; - CollisionShape2D *node; + CanvasItemEditor *canvas_item_editor = nullptr; + CollisionShape2D *node = nullptr; Vector<Point2> handles; @@ -93,14 +89,13 @@ public: void forward_canvas_draw_over_viewport(Control *p_overlay); void edit(Node *p_node); - CollisionShape2DEditor(EditorNode *p_editor); + CollisionShape2DEditor(); }; class CollisionShape2DEditorPlugin : public EditorPlugin { GDCLASS(CollisionShape2DEditorPlugin, EditorPlugin); - CollisionShape2DEditor *collision_shape_2d_editor; - EditorNode *editor; + CollisionShape2DEditor *collision_shape_2d_editor = nullptr; public: virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return collision_shape_2d_editor->forward_canvas_gui_input(p_event); } @@ -112,8 +107,8 @@ public: virtual bool handles(Object *p_obj) const override; virtual void make_visible(bool visible) override; - CollisionShape2DEditorPlugin(EditorNode *p_editor); + CollisionShape2DEditorPlugin(); ~CollisionShape2DEditorPlugin(); }; -#endif //COLLISION_SHAPE_2D_EDITOR_PLUGIN_H +#endif // COLLISION_SHAPE_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp new file mode 100644 index 0000000000..3bf2b95c26 --- /dev/null +++ b/editor/plugins/control_editor_plugin.cpp @@ -0,0 +1,1051 @@ +/**************************************************************************/ +/* control_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 "control_editor_plugin.h" + +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/plugins/canvas_item_editor_plugin.h" +#include "scene/gui/grid_container.h" +#include "scene/gui/separator.h" + +// Inspector controls. + +void ControlPositioningWarning::_update_warning() { + if (!control_node) { + title_icon->set_texture(nullptr); + title_label->set_text(""); + hint_label->set_text(""); + return; + } + + Node *parent_node = control_node->get_parent_control(); + if (!parent_node) { + title_icon->set_texture(get_theme_icon(SNAME("SubViewport"), SNAME("EditorIcons"))); + title_label->set_text(TTR("This node doesn't have a control parent.")); + hint_label->set_text(TTR("Use the appropriate layout properties depending on where you are going to put it.")); + } else if (Object::cast_to<Container>(parent_node)) { + title_icon->set_texture(get_theme_icon(SNAME("ContainerLayout"), SNAME("EditorIcons"))); + title_label->set_text(TTR("This node is a child of a container.")); + hint_label->set_text(TTR("Use container properties for positioning.")); + } else { + title_icon->set_texture(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); + title_label->set_text(TTR("This node is a child of a regular control.")); + hint_label->set_text(TTR("Use anchors and the rectangle for positioning.")); + } + + bg_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg_group_note"), SNAME("EditorProperty"))); +} + +void ControlPositioningWarning::_update_toggler() { + Ref<Texture2D> arrow; + if (hint_label->is_visible()) { + arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); + set_tooltip_text(TTR("Collapse positioning hint.")); + } else { + if (is_layout_rtl()) { + arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + } else { + arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + } + set_tooltip_text(TTR("Expand positioning hint.")); + } + + hint_icon->set_texture(arrow); +} + +void ControlPositioningWarning::set_control(Control *p_node) { + control_node = p_node; + _update_warning(); +} + +void ControlPositioningWarning::gui_input(const Ref<InputEvent> &p_event) { + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + bool state = !hint_label->is_visible(); + + hint_filler_left->set_visible(state); + hint_label->set_visible(state); + hint_filler_right->set_visible(state); + + _update_toggler(); + } +} + +void ControlPositioningWarning::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + _update_warning(); + _update_toggler(); + break; + } +} + +ControlPositioningWarning::ControlPositioningWarning() { + set_mouse_filter(MOUSE_FILTER_STOP); + + bg_panel = memnew(PanelContainer); + bg_panel->set_mouse_filter(MOUSE_FILTER_IGNORE); + add_child(bg_panel); + + grid = memnew(GridContainer); + grid->set_columns(3); + bg_panel->add_child(grid); + + title_icon = memnew(TextureRect); + title_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); + grid->add_child(title_icon); + + title_label = memnew(Label); + title_label->set_autowrap_mode(TextServer::AutowrapMode::AUTOWRAP_WORD); + title_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + title_label->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + grid->add_child(title_label); + + hint_icon = memnew(TextureRect); + hint_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); + grid->add_child(hint_icon); + + // Filler. + hint_filler_left = memnew(Control); + hint_filler_left->hide(); + grid->add_child(hint_filler_left); + + hint_label = memnew(Label); + hint_label->set_autowrap_mode(TextServer::AutowrapMode::AUTOWRAP_WORD); + hint_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hint_label->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + hint_label->hide(); + grid->add_child(hint_label); + + // Filler. + hint_filler_right = memnew(Control); + hint_filler_right->hide(); + grid->add_child(hint_filler_right); +} + +void EditorPropertyAnchorsPreset::_set_read_only(bool p_read_only) { + options->set_disabled(p_read_only); +}; + +void EditorPropertyAnchorsPreset::_option_selected(int p_which) { + int64_t val = options->get_item_metadata(p_which); + emit_changed(get_edited_property(), val); +} + +void EditorPropertyAnchorsPreset::update_property() { + int64_t which = get_edited_object()->get(get_edited_property()); + + for (int i = 0; i < options->get_item_count(); i++) { + Variant val = options->get_item_metadata(i); + if (val != Variant() && which == (int64_t)val) { + options->select(i); + return; + } + } +} + +void EditorPropertyAnchorsPreset::setup(const Vector<String> &p_options) { + options->clear(); + + Vector<String> split_after; + split_after.append("Custom"); + split_after.append("PresetFullRect"); + split_after.append("PresetBottomLeft"); + split_after.append("PresetCenter"); + + for (int i = 0, j = 0; i < p_options.size(); i++, j++) { + Vector<String> text_split = p_options[i].split(":"); + int64_t current_val = text_split[1].to_int(); + + String option_name = text_split[0]; + if (option_name.begins_with("Preset")) { + String preset_name = option_name.trim_prefix("Preset"); + String humanized_name = preset_name.capitalize(); + String icon_name = "ControlAlign" + preset_name; + options->add_icon_item(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(icon_name, "EditorIcons"), humanized_name); + } else { + options->add_item(option_name); + } + + options->set_item_metadata(j, current_val); + if (split_after.has(option_name)) { + options->add_separator(); + j++; + } + } +} + +EditorPropertyAnchorsPreset::EditorPropertyAnchorsPreset() { + options = memnew(OptionButton); + options->set_clip_text(true); + options->set_flat(true); + add_child(options); + add_focusable(options); + options->connect("item_selected", callable_mp(this, &EditorPropertyAnchorsPreset::_option_selected)); +} + +void EditorPropertySizeFlags::_set_read_only(bool p_read_only) { + for (CheckBox *check : flag_checks) { + check->set_disabled(p_read_only); + } + flag_presets->set_disabled(p_read_only); +}; + +void EditorPropertySizeFlags::_preset_selected(int p_which) { + int preset = flag_presets->get_item_id(p_which); + if (preset == SIZE_FLAGS_PRESET_CUSTOM) { + flag_options->set_visible(true); + return; + } + flag_options->set_visible(false); + + uint32_t value = 0; + switch (preset) { + case SIZE_FLAGS_PRESET_FILL: + value = Control::SIZE_FILL; + break; + case SIZE_FLAGS_PRESET_SHRINK_BEGIN: + value = Control::SIZE_SHRINK_BEGIN; + break; + case SIZE_FLAGS_PRESET_SHRINK_CENTER: + value = Control::SIZE_SHRINK_CENTER; + break; + case SIZE_FLAGS_PRESET_SHRINK_END: + value = Control::SIZE_SHRINK_END; + break; + } + + bool is_expand = flag_expand->is_visible() && flag_expand->is_pressed(); + if (is_expand) { + value |= Control::SIZE_EXPAND; + } + + emit_changed(get_edited_property(), value); +} + +void EditorPropertySizeFlags::_expand_toggled() { + uint32_t value = get_edited_object()->get(get_edited_property()); + + if (flag_expand->is_visible() && flag_expand->is_pressed()) { + value |= Control::SIZE_EXPAND; + } else { + value ^= Control::SIZE_EXPAND; + } + + // Keep the custom preset selected as we toggle individual flags. + keep_selected_preset = true; + emit_changed(get_edited_property(), value); +} + +void EditorPropertySizeFlags::_flag_toggled() { + uint32_t value = 0; + for (int i = 0; i < flag_checks.size(); i++) { + if (flag_checks[i]->is_pressed()) { + int flag_value = flag_checks[i]->get_meta("_value"); + value |= flag_value; + } + } + + bool is_expand = flag_expand->is_visible() && flag_expand->is_pressed(); + if (is_expand) { + value |= Control::SIZE_EXPAND; + } + + // Keep the custom preset selected as we toggle individual flags. + keep_selected_preset = true; + emit_changed(get_edited_property(), value); +} + +void EditorPropertySizeFlags::update_property() { + uint32_t value = get_edited_object()->get(get_edited_property()); + + for (int i = 0; i < flag_checks.size(); i++) { + int flag_value = flag_checks[i]->get_meta("_value"); + if (value & flag_value) { + flag_checks[i]->set_pressed(true); + } else { + flag_checks[i]->set_pressed(false); + } + } + + bool is_expand = value & Control::SIZE_EXPAND; + flag_expand->set_pressed(is_expand); + + if (keep_selected_preset) { + keep_selected_preset = false; + return; + } + + FlagPreset preset = SIZE_FLAGS_PRESET_CUSTOM; + if (value == Control::SIZE_FILL || value == (Control::SIZE_FILL | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_FILL; + } else if (value == Control::SIZE_SHRINK_BEGIN || value == (Control::SIZE_SHRINK_BEGIN | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_SHRINK_BEGIN; + } else if (value == Control::SIZE_SHRINK_CENTER || value == (Control::SIZE_SHRINK_CENTER | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_SHRINK_CENTER; + } else if (value == Control::SIZE_SHRINK_END || value == (Control::SIZE_SHRINK_END | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_SHRINK_END; + } + + int preset_idx = flag_presets->get_item_index(preset); + if (preset_idx >= 0) { + flag_presets->select(preset_idx); + } + flag_options->set_visible(preset == SIZE_FLAGS_PRESET_CUSTOM); +} + +void EditorPropertySizeFlags::setup(const Vector<String> &p_options, bool p_vertical) { + vertical = p_vertical; + + if (p_options.size() == 0) { + flag_presets->clear(); + flag_presets->add_item(TTR("Container Default")); + flag_presets->set_disabled(true); + flag_expand->set_visible(false); + return; + } + + HashMap<int, String> flags; + for (int i = 0, j = 0; i < p_options.size(); i++, j++) { + Vector<String> text_split = p_options[i].split(":"); + int64_t current_val = text_split[1].to_int(); + flags[current_val] = text_split[0]; + + if (current_val == SIZE_EXPAND) { + continue; + } + + CheckBox *cb = memnew(CheckBox); + cb->set_text(text_split[0]); + cb->set_clip_text(true); + cb->set_meta("_value", current_val); + cb->connect("pressed", callable_mp(this, &EditorPropertySizeFlags::_flag_toggled)); + add_focusable(cb); + + flag_options->add_child(cb); + flag_checks.append(cb); + } + + Control *gui_base = EditorNode::get_singleton()->get_gui_base(); + String wide_preset_icon = SNAME("ControlAlignHCenterWide"); + String begin_preset_icon = SNAME("ControlAlignCenterLeft"); + String end_preset_icon = SNAME("ControlAlignCenterRight"); + if (vertical) { + wide_preset_icon = SNAME("ControlAlignVCenterWide"); + begin_preset_icon = SNAME("ControlAlignCenterTop"); + end_preset_icon = SNAME("ControlAlignCenterBottom"); + } + + flag_presets->clear(); + if (flags.has(SIZE_FILL)) { + flag_presets->add_icon_item(gui_base->get_theme_icon(wide_preset_icon, SNAME("EditorIcons")), TTR("Fill"), SIZE_FLAGS_PRESET_FILL); + } + // Shrink Begin is the same as no flags at all, as such it cannot be disabled. + flag_presets->add_icon_item(gui_base->get_theme_icon(begin_preset_icon, SNAME("EditorIcons")), TTR("Shrink Begin"), SIZE_FLAGS_PRESET_SHRINK_BEGIN); + if (flags.has(SIZE_SHRINK_CENTER)) { + flag_presets->add_icon_item(gui_base->get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), SIZE_FLAGS_PRESET_SHRINK_CENTER); + } + if (flags.has(SIZE_SHRINK_END)) { + flag_presets->add_icon_item(gui_base->get_theme_icon(end_preset_icon, SNAME("EditorIcons")), TTR("Shrink End"), SIZE_FLAGS_PRESET_SHRINK_END); + } + flag_presets->add_separator(); + flag_presets->add_item(TTR("Custom"), SIZE_FLAGS_PRESET_CUSTOM); + + flag_expand->set_visible(flags.has(SIZE_EXPAND)); +} + +EditorPropertySizeFlags::EditorPropertySizeFlags() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + + flag_presets = memnew(OptionButton); + flag_presets->set_clip_text(true); + flag_presets->set_flat(true); + vb->add_child(flag_presets); + add_focusable(flag_presets); + set_label_reference(flag_presets); + flag_presets->connect("item_selected", callable_mp(this, &EditorPropertySizeFlags::_preset_selected)); + + flag_options = memnew(VBoxContainer); + flag_options->hide(); + vb->add_child(flag_options); + + flag_expand = memnew(CheckBox); + flag_expand->set_text(TTR("Expand")); + vb->add_child(flag_expand); + add_focusable(flag_expand); + flag_expand->connect("pressed", callable_mp(this, &EditorPropertySizeFlags::_expand_toggled)); +} + +bool EditorInspectorPluginControl::can_handle(Object *p_object) { + return Object::cast_to<Control>(p_object) != nullptr; +} + +void EditorInspectorPluginControl::parse_group(Object *p_object, const String &p_group) { + Control *control = Object::cast_to<Control>(p_object); + if (!control || p_group != "Layout") { + return; + } + + ControlPositioningWarning *pos_warning = memnew(ControlPositioningWarning); + pos_warning->set_control(control); + add_custom_control(pos_warning); +} + +bool EditorInspectorPluginControl::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) { + Control *control = Object::cast_to<Control>(p_object); + if (!control) { + return false; + } + + if (p_path == "anchors_preset") { + EditorPropertyAnchorsPreset *prop_editor = memnew(EditorPropertyAnchorsPreset); + Vector<String> options = p_hint_text.split(","); + prop_editor->setup(options); + add_property_editor(p_path, prop_editor); + + return true; + } + + if (p_path == "size_flags_horizontal" || p_path == "size_flags_vertical") { + EditorPropertySizeFlags *prop_editor = memnew(EditorPropertySizeFlags); + Vector<String> options; + if (!p_hint_text.is_empty()) { + options = p_hint_text.split(","); + } + prop_editor->setup(options, p_path == "size_flags_vertical"); + add_property_editor(p_path, prop_editor); + + return true; + } + + return false; +} + +// Toolbars controls. + +Size2 ControlEditorPopupButton::get_minimum_size() const { + Vector2 base_size = Vector2(26, 26) * EDSCALE; + + if (arrow_icon.is_null()) { + return base_size; + } + + Vector2 final_size; + final_size.x = base_size.x + arrow_icon->get_width(); + final_size.y = MAX(base_size.y, arrow_icon->get_height()); + + return final_size; +} + +void ControlEditorPopupButton::toggled(bool p_pressed) { + if (!p_pressed) { + return; + } + + Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); + + popup_panel->set_size(Size2(size.width, 0)); + Point2 gp = get_screen_position(); + gp.y += size.y; + if (is_layout_rtl()) { + gp.x += size.width - popup_panel->get_size().width; + } + popup_panel->set_position(gp); + + popup_panel->popup(); +} + +void ControlEditorPopupButton::_popup_visibility_changed(bool p_visible) { + set_pressed(p_visible); +} + +void ControlEditorPopupButton::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + arrow_icon = get_theme_icon("select_arrow", "Tree"); + } break; + + case NOTIFICATION_DRAW: { + if (arrow_icon.is_valid()) { + Vector2 arrow_pos = Point2(26, 0) * EDSCALE; + arrow_pos.y = get_size().y / 2 - arrow_icon->get_height() / 2; + draw_texture(arrow_icon, arrow_pos); + } + } break; + + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + popup_panel->set_layout_direction((Window::LayoutDirection)get_layout_direction()); + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible_in_tree()) { + popup_panel->hide(); + } + } break; + } +} + +ControlEditorPopupButton::ControlEditorPopupButton() { + set_flat(true); + set_toggle_mode(true); + set_focus_mode(FOCUS_NONE); + + popup_panel = memnew(PopupPanel); + popup_panel->set_theme_type_variation("ControlEditorPopupPanel"); + add_child(popup_panel); + popup_panel->connect("about_to_popup", callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(true)); + popup_panel->connect("popup_hide", callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(false)); + + popup_vbox = memnew(VBoxContainer); + popup_panel->add_child(popup_vbox); +} + +void ControlEditorPresetPicker::_add_row_button(HBoxContainer *p_row, const int p_preset, const String &p_name) { + ERR_FAIL_COND(preset_buttons.has(p_preset)); + + Button *b = memnew(Button); + b->set_custom_minimum_size(Size2i(36, 36) * EDSCALE); + b->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); + b->set_tooltip_text(p_name); + b->set_flat(true); + p_row->add_child(b); + b->connect("pressed", callable_mp(this, &ControlEditorPresetPicker::_preset_button_pressed).bind(p_preset)); + + preset_buttons[p_preset] = b; +} + +void ControlEditorPresetPicker::_add_separator(BoxContainer *p_box, Separator *p_separator) { + p_separator->add_theme_constant_override("separation", grid_separation); + p_separator->set_custom_minimum_size(Size2i(1, 1)); + p_box->add_child(p_separator); +} + +void AnchorPresetPicker::_preset_button_pressed(const int p_preset) { + emit_signal("anchors_preset_selected", p_preset); +} + +void AnchorPresetPicker::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + preset_buttons[PRESET_TOP_LEFT]->set_icon(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons"))); + preset_buttons[PRESET_CENTER_TOP]->set_icon(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons"))); + preset_buttons[PRESET_TOP_RIGHT]->set_icon(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons"))); + + preset_buttons[PRESET_CENTER_LEFT]->set_icon(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons"))); + preset_buttons[PRESET_CENTER]->set_icon(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons"))); + preset_buttons[PRESET_CENTER_RIGHT]->set_icon(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons"))); + + preset_buttons[PRESET_BOTTOM_LEFT]->set_icon(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons"))); + preset_buttons[PRESET_CENTER_BOTTOM]->set_icon(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons"))); + preset_buttons[PRESET_BOTTOM_RIGHT]->set_icon(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons"))); + + preset_buttons[PRESET_TOP_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons"))); + preset_buttons[PRESET_HCENTER_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons"))); + preset_buttons[PRESET_BOTTOM_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons"))); + + preset_buttons[PRESET_LEFT_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons"))); + preset_buttons[PRESET_VCENTER_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons"))); + preset_buttons[PRESET_RIGHT_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons"))); + + preset_buttons[PRESET_FULL_RECT]->set_icon(get_theme_icon(SNAME("ControlAlignFullRect"), SNAME("EditorIcons"))); + } break; + } +} + +void AnchorPresetPicker::_bind_methods() { + ADD_SIGNAL(MethodInfo("anchors_preset_selected", PropertyInfo(Variant::INT, "preset"))); +} + +AnchorPresetPicker::AnchorPresetPicker() { + VBoxContainer *main_vb = memnew(VBoxContainer); + main_vb->add_theme_constant_override("separation", grid_separation); + add_child(main_vb); + + HBoxContainer *top_row = memnew(HBoxContainer); + top_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); + top_row->add_theme_constant_override("separation", grid_separation); + main_vb->add_child(top_row); + + _add_row_button(top_row, PRESET_TOP_LEFT, TTR("Top Left")); + _add_row_button(top_row, PRESET_CENTER_TOP, TTR("Center Top")); + _add_row_button(top_row, PRESET_TOP_RIGHT, TTR("Top Right")); + _add_separator(top_row, memnew(VSeparator)); + _add_row_button(top_row, PRESET_TOP_WIDE, TTR("Top Wide")); + + HBoxContainer *mid_row = memnew(HBoxContainer); + mid_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); + mid_row->add_theme_constant_override("separation", grid_separation); + main_vb->add_child(mid_row); + + _add_row_button(mid_row, PRESET_CENTER_LEFT, TTR("Center Left")); + _add_row_button(mid_row, PRESET_CENTER, TTR("Center")); + _add_row_button(mid_row, PRESET_CENTER_RIGHT, TTR("Center Right")); + _add_separator(mid_row, memnew(VSeparator)); + _add_row_button(mid_row, PRESET_HCENTER_WIDE, TTR("HCenter Wide")); + + HBoxContainer *bot_row = memnew(HBoxContainer); + bot_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); + bot_row->add_theme_constant_override("separation", grid_separation); + main_vb->add_child(bot_row); + + _add_row_button(bot_row, PRESET_BOTTOM_LEFT, TTR("Bottom Left")); + _add_row_button(bot_row, PRESET_CENTER_BOTTOM, TTR("Center Bottom")); + _add_row_button(bot_row, PRESET_BOTTOM_RIGHT, TTR("Bottom Right")); + _add_separator(bot_row, memnew(VSeparator)); + _add_row_button(bot_row, PRESET_BOTTOM_WIDE, TTR("Bottom Wide")); + + _add_separator(main_vb, memnew(HSeparator)); + + HBoxContainer *extra_row = memnew(HBoxContainer); + extra_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); + extra_row->add_theme_constant_override("separation", grid_separation); + main_vb->add_child(extra_row); + + _add_row_button(extra_row, PRESET_LEFT_WIDE, TTR("Left Wide")); + _add_row_button(extra_row, PRESET_VCENTER_WIDE, TTR("VCenter Wide")); + _add_row_button(extra_row, PRESET_RIGHT_WIDE, TTR("Right Wide")); + _add_separator(extra_row, memnew(VSeparator)); + _add_row_button(extra_row, PRESET_FULL_RECT, TTR("Full Rect")); +} + +void SizeFlagPresetPicker::_preset_button_pressed(const int p_preset) { + int flags = (SizeFlags)p_preset; + if (expand_button->is_pressed()) { + flags |= SIZE_EXPAND; + } + + emit_signal("size_flags_selected", flags); +} + +void SizeFlagPresetPicker::set_allowed_flags(Vector<SizeFlags> &p_flags) { + preset_buttons[SIZE_SHRINK_BEGIN]->set_disabled(!p_flags.has(SIZE_SHRINK_BEGIN)); + preset_buttons[SIZE_SHRINK_CENTER]->set_disabled(!p_flags.has(SIZE_SHRINK_CENTER)); + preset_buttons[SIZE_SHRINK_END]->set_disabled(!p_flags.has(SIZE_SHRINK_END)); + preset_buttons[SIZE_FILL]->set_disabled(!p_flags.has(SIZE_FILL)); + + expand_button->set_disabled(!p_flags.has(SIZE_EXPAND)); + if (p_flags.has(SIZE_EXPAND)) { + expand_button->set_tooltip_text(TTR("Enable to also set the Expand flag.\nDisable to only set Shrink/Fill flags.")); + } else { + expand_button->set_pressed(false); + expand_button->set_tooltip_text(TTR("Some parents of the selected nodes do not support the Expand flag.")); + } +} + +void SizeFlagPresetPicker::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + if (vertical) { + preset_buttons[SIZE_SHRINK_BEGIN]->set_icon(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons"))); + preset_buttons[SIZE_SHRINK_CENTER]->set_icon(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons"))); + preset_buttons[SIZE_SHRINK_END]->set_icon(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons"))); + + preset_buttons[SIZE_FILL]->set_icon(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons"))); + } else { + preset_buttons[SIZE_SHRINK_BEGIN]->set_icon(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons"))); + preset_buttons[SIZE_SHRINK_CENTER]->set_icon(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons"))); + preset_buttons[SIZE_SHRINK_END]->set_icon(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons"))); + + preset_buttons[SIZE_FILL]->set_icon(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons"))); + } + } break; + } +} + +void SizeFlagPresetPicker::_bind_methods() { + ADD_SIGNAL(MethodInfo("size_flags_selected", PropertyInfo(Variant::INT, "size_flags"))); +} + +SizeFlagPresetPicker::SizeFlagPresetPicker(bool p_vertical) { + vertical = p_vertical; + + VBoxContainer *main_vb = memnew(VBoxContainer); + add_child(main_vb); + + HBoxContainer *main_row = memnew(HBoxContainer); + main_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); + main_row->add_theme_constant_override("separation", grid_separation); + main_vb->add_child(main_row); + + _add_row_button(main_row, SIZE_SHRINK_BEGIN, TTR("Shrink Begin")); + _add_row_button(main_row, SIZE_SHRINK_CENTER, TTR("Shrink Center")); + _add_row_button(main_row, SIZE_SHRINK_END, TTR("Shrink End")); + _add_separator(main_row, memnew(VSeparator)); + _add_row_button(main_row, SIZE_FILL, TTR("Fill")); + + expand_button = memnew(CheckBox); + expand_button->set_flat(true); + expand_button->set_text(TTR("Align with Expand")); + expand_button->set_tooltip_text(TTR("Enable to also set the Expand flag.\nDisable to only set Shrink/Fill flags.")); + main_vb->add_child(expand_button); +} + +// Toolbar. + +void ControlEditorToolbar::_anchors_preset_selected(int p_preset) { + LayoutPreset preset = (LayoutPreset)p_preset; + List<Node *> selection = editor_selection->get_selected_node_list(); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Change Anchors, Offsets, Grow Direction")); + + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + undo_redo->add_do_property(control, "layout_mode", LayoutMode::LAYOUT_MODE_ANCHORS); + undo_redo->add_do_property(control, "anchors_preset", preset); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); + + anchors_mode = false; + anchor_mode_button->set_pressed(anchors_mode); +} + +void ControlEditorToolbar::_anchors_to_current_ratio() { + List<Node *> selection = editor_selection->get_selected_node_list(); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Change Anchors, Offsets (Keep Ratio)")); + + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + Point2 top_left_anchor = _position_to_anchor(control, Point2()); + Point2 bottom_right_anchor = _position_to_anchor(control, control->get_size()); + undo_redo->add_do_method(control, "set_anchor", SIDE_LEFT, top_left_anchor.x, false, true); + undo_redo->add_do_method(control, "set_anchor", SIDE_RIGHT, bottom_right_anchor.x, false, true); + undo_redo->add_do_method(control, "set_anchor", SIDE_TOP, top_left_anchor.y, false, true); + undo_redo->add_do_method(control, "set_anchor", SIDE_BOTTOM, bottom_right_anchor.y, false, true); + undo_redo->add_do_method(control, "set_meta", "_edit_use_anchors_", true); + + const bool use_anchors = control->get_meta("_edit_use_anchors_", false); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + if (use_anchors) { + undo_redo->add_undo_method(control, "set_meta", "_edit_use_anchors_", true); + } else { + undo_redo->add_undo_method(control, "remove_meta", "_edit_use_anchors_"); + } + + anchors_mode = true; + anchor_mode_button->set_pressed(anchors_mode); + } + } + + undo_redo->commit_action(); +} + +void ControlEditorToolbar::_anchor_mode_toggled(bool p_status) { + List<Control *> selection = _get_edited_controls(); + for (Control *E : selection) { + if (Object::cast_to<Container>(E->get_parent())) { + continue; + } + + if (p_status) { + E->set_meta("_edit_use_anchors_", true); + } else { + E->remove_meta("_edit_use_anchors_"); + } + } + + anchors_mode = p_status; + CanvasItemEditor::get_singleton()->update_viewport(); +} + +void ControlEditorToolbar::_container_flags_selected(int p_flags, bool p_vertical) { + List<Node *> selection = editor_selection->get_selected_node_list(); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + if (p_vertical) { + undo_redo->create_action(TTR("Change Vertical Size Flags")); + } else { + undo_redo->create_action(TTR("Change Horizontal Size Flags")); + } + + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + if (p_vertical) { + undo_redo->add_do_method(control, "set_v_size_flags", p_flags); + } else { + undo_redo->add_do_method(control, "set_h_size_flags", p_flags); + } + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); +} + +Vector2 ControlEditorToolbar::_position_to_anchor(const Control *p_control, Vector2 position) { + ERR_FAIL_COND_V(!p_control, Vector2()); + + Rect2 parent_rect = p_control->get_parent_anchorable_rect(); + + Vector2 output; + if (p_control->is_layout_rtl()) { + output.x = (parent_rect.size.x == 0) ? 0.0 : (parent_rect.size.x - p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; + } else { + output.x = (parent_rect.size.x == 0) ? 0.0 : (p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; + } + output.y = (parent_rect.size.y == 0) ? 0.0 : (p_control->get_transform().xform(position).y - parent_rect.position.y) / parent_rect.size.y; + return output; +} + +bool ControlEditorToolbar::_is_node_locked(const Node *p_node) { + return p_node->get_meta("_edit_lock_", false); +} + +List<Control *> ControlEditorToolbar::_get_edited_controls() { + List<Control *> selection; + for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { + Control *control = Object::cast_to<Control>(E.key); + if (control && control->is_visible_in_tree() && control->get_viewport() == EditorNode::get_singleton()->get_scene_root() && !_is_node_locked(control)) { + selection.push_back(control); + } + } + + return selection; +} + +void ControlEditorToolbar::_selection_changed() { + // Update toolbar visibility. + bool has_controls = false; + bool has_control_parents = false; + bool has_container_parents = false; + + // Also update which size flags can be configured for the selected nodes. + Vector<SizeFlags> allowed_h_flags = { + SIZE_SHRINK_BEGIN, + SIZE_SHRINK_CENTER, + SIZE_SHRINK_END, + SIZE_FILL, + SIZE_EXPAND, + }; + Vector<SizeFlags> allowed_v_flags = { + SIZE_SHRINK_BEGIN, + SIZE_SHRINK_CENTER, + SIZE_SHRINK_END, + SIZE_FILL, + SIZE_EXPAND, + }; + + for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { + Control *control = Object::cast_to<Control>(E.key); + if (!control) { + continue; + } + has_controls = true; + + if (Object::cast_to<Control>(control->get_parent())) { + has_control_parents = true; + } + if (Object::cast_to<Container>(control->get_parent())) { + has_container_parents = true; + + Container *parent_container = Object::cast_to<Container>(control->get_parent()); + + Vector<int> container_h_flags = parent_container->get_allowed_size_flags_horizontal(); + Vector<SizeFlags> tmp_flags = allowed_h_flags.duplicate(); + for (int i = 0; i < allowed_h_flags.size(); i++) { + if (!container_h_flags.has((int)allowed_h_flags[i])) { + tmp_flags.erase(allowed_h_flags[i]); + } + } + allowed_h_flags = tmp_flags; + + Vector<int> container_v_flags = parent_container->get_allowed_size_flags_vertical(); + tmp_flags = allowed_v_flags.duplicate(); + for (int i = 0; i < allowed_v_flags.size(); i++) { + if (!container_v_flags.has((int)allowed_v_flags[i])) { + tmp_flags.erase(allowed_v_flags[i]); + } + } + allowed_v_flags = tmp_flags; + } + } + + // Set general toolbar visibility. + set_visible(has_controls); + + // Set anchor tools visibility. + if (has_controls && (!has_control_parents || !has_container_parents)) { + anchors_button->set_visible(true); + anchor_mode_button->set_visible(true); + + // Update anchor mode. + int nb_valid_controls = 0; + int nb_anchors_mode = 0; + + List<Node *> selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (!control) { + continue; + } + if (Object::cast_to<Container>(control->get_parent())) { + continue; + } + + nb_valid_controls++; + if (control->get_meta("_edit_use_anchors_", false)) { + nb_anchors_mode++; + } + } + + anchors_mode = (nb_valid_controls == nb_anchors_mode); + anchor_mode_button->set_pressed(anchors_mode); + } else { + anchors_button->set_visible(false); + anchor_mode_button->set_visible(false); + anchor_mode_button->set_pressed(false); + } + + // Set container tools visibility. + if (has_controls && (!has_control_parents || has_container_parents)) { + containers_button->set_visible(true); + + // Update allowed size flags. + if (has_container_parents) { + container_h_picker->set_allowed_flags(allowed_h_flags); + container_v_picker->set_allowed_flags(allowed_v_flags); + } else { + Vector<SizeFlags> allowed_all_flags = { + SIZE_SHRINK_BEGIN, + SIZE_SHRINK_CENTER, + SIZE_SHRINK_END, + SIZE_FILL, + SIZE_EXPAND, + }; + + container_h_picker->set_allowed_flags(allowed_all_flags); + container_v_picker->set_allowed_flags(allowed_all_flags); + } + } else { + containers_button->set_visible(false); + } +} + +void ControlEditorToolbar::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + anchors_button->set_icon(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); + anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); + containers_button->set_icon(get_theme_icon(SNAME("ContainerLayout"), SNAME("EditorIcons"))); + } break; + } +} + +ControlEditorToolbar::ControlEditorToolbar() { + add_child(memnew(VSeparator)); + + // Anchor and offset tools. + anchors_button = memnew(ControlEditorPopupButton); + anchors_button->set_tooltip_text(TTR("Presets for the anchor and offset values of a Control node.")); + add_child(anchors_button); + + Label *anchors_label = memnew(Label); + anchors_label->set_text(TTR("Anchor preset")); + anchors_button->get_popup_hbox()->add_child(anchors_label); + AnchorPresetPicker *anchors_picker = memnew(AnchorPresetPicker); + anchors_picker->set_h_size_flags(SIZE_SHRINK_CENTER); + anchors_button->get_popup_hbox()->add_child(anchors_picker); + anchors_picker->connect("anchors_preset_selected", callable_mp(this, &ControlEditorToolbar::_anchors_preset_selected)); + + anchors_button->get_popup_hbox()->add_child(memnew(HSeparator)); + + Button *keep_ratio_button = memnew(Button); + keep_ratio_button->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + keep_ratio_button->set_text(TTR("Set to Current Ratio")); + keep_ratio_button->set_tooltip_text(TTR("Adjust anchors and offsets to match the current rect size.")); + anchors_button->get_popup_hbox()->add_child(keep_ratio_button); + keep_ratio_button->connect("pressed", callable_mp(this, &ControlEditorToolbar::_anchors_to_current_ratio)); + + anchor_mode_button = memnew(Button); + anchor_mode_button->set_flat(true); + anchor_mode_button->set_toggle_mode(true); + anchor_mode_button->set_tooltip_text(TTR("When active, moving Control nodes changes their anchors instead of their offsets.")); + add_child(anchor_mode_button); + anchor_mode_button->connect("toggled", callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled)); + + // Container tools. + containers_button = memnew(ControlEditorPopupButton); + containers_button->set_tooltip_text(TTR("Sizing settings for children of a Container node.")); + add_child(containers_button); + + Label *container_h_label = memnew(Label); + container_h_label->set_text(TTR("Horizontal alignment")); + containers_button->get_popup_hbox()->add_child(container_h_label); + container_h_picker = memnew(SizeFlagPresetPicker(false)); + containers_button->get_popup_hbox()->add_child(container_h_picker); + container_h_picker->connect("size_flags_selected", callable_mp(this, &ControlEditorToolbar::_container_flags_selected).bind(false)); + + containers_button->get_popup_hbox()->add_child(memnew(HSeparator)); + + Label *container_v_label = memnew(Label); + container_v_label->set_text(TTR("Vertical alignment")); + containers_button->get_popup_hbox()->add_child(container_v_label); + container_v_picker = memnew(SizeFlagPresetPicker(true)); + containers_button->get_popup_hbox()->add_child(container_v_picker); + container_v_picker->connect("size_flags_selected", callable_mp(this, &ControlEditorToolbar::_container_flags_selected).bind(true)); + + // Editor connections. + editor_selection = EditorNode::get_singleton()->get_editor_selection(); + editor_selection->add_editor_plugin(this); + editor_selection->connect("selection_changed", callable_mp(this, &ControlEditorToolbar::_selection_changed)); + + singleton = this; +} + +ControlEditorToolbar *ControlEditorToolbar::singleton = nullptr; + +// Editor plugin. + +ControlEditorPlugin::ControlEditorPlugin() { + toolbar = memnew(ControlEditorToolbar); + toolbar->hide(); + add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar); + + Ref<EditorInspectorPluginControl> plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} diff --git a/editor/plugins/control_editor_plugin.h b/editor/plugins/control_editor_plugin.h new file mode 100644 index 0000000000..19e004a390 --- /dev/null +++ b/editor/plugins/control_editor_plugin.h @@ -0,0 +1,255 @@ +/**************************************************************************/ +/* control_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 CONTROL_EDITOR_PLUGIN_H +#define CONTROL_EDITOR_PLUGIN_H + +#include "editor/editor_inspector.h" +#include "editor/editor_plugin.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/check_box.h" +#include "scene/gui/control.h" +#include "scene/gui/label.h" +#include "scene/gui/margin_container.h" +#include "scene/gui/option_button.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/popup.h" +#include "scene/gui/separator.h" +#include "scene/gui/texture_rect.h" + +class GridContainer; + +// Inspector controls. +class ControlPositioningWarning : public MarginContainer { + GDCLASS(ControlPositioningWarning, MarginContainer); + + Control *control_node = nullptr; + + PanelContainer *bg_panel = nullptr; + GridContainer *grid = nullptr; + TextureRect *title_icon = nullptr; + TextureRect *hint_icon = nullptr; + Label *title_label = nullptr; + Label *hint_label = nullptr; + Control *hint_filler_left = nullptr; + Control *hint_filler_right = nullptr; + + void _update_warning(); + void _update_toggler(); + virtual void gui_input(const Ref<InputEvent> &p_event) override; + +protected: + void _notification(int p_notification); + +public: + void set_control(Control *p_node); + + ControlPositioningWarning(); +}; + +class EditorPropertyAnchorsPreset : public EditorProperty { + GDCLASS(EditorPropertyAnchorsPreset, EditorProperty); + OptionButton *options = nullptr; + + void _option_selected(int p_which); + +protected: + virtual void _set_read_only(bool p_read_only) override; + +public: + void setup(const Vector<String> &p_options); + virtual void update_property() override; + EditorPropertyAnchorsPreset(); +}; + +class EditorPropertySizeFlags : public EditorProperty { + GDCLASS(EditorPropertySizeFlags, EditorProperty); + + enum FlagPreset { + SIZE_FLAGS_PRESET_FILL, + SIZE_FLAGS_PRESET_SHRINK_BEGIN, + SIZE_FLAGS_PRESET_SHRINK_CENTER, + SIZE_FLAGS_PRESET_SHRINK_END, + SIZE_FLAGS_PRESET_CUSTOM, + }; + + OptionButton *flag_presets = nullptr; + CheckBox *flag_expand = nullptr; + VBoxContainer *flag_options = nullptr; + Vector<CheckBox *> flag_checks; + + bool vertical = false; + + bool keep_selected_preset = false; + + void _preset_selected(int p_which); + void _expand_toggled(); + void _flag_toggled(); + +protected: + virtual void _set_read_only(bool p_read_only) override; + +public: + void setup(const Vector<String> &p_options, bool p_vertical); + virtual void update_property() override; + EditorPropertySizeFlags(); +}; + +class EditorInspectorPluginControl : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginControl, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_group(Object *p_object, const String &p_group) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false) override; +}; + +// Toolbar controls. +class ControlEditorPopupButton : public Button { + GDCLASS(ControlEditorPopupButton, Button); + + Ref<Texture2D> arrow_icon; + + PopupPanel *popup_panel = nullptr; + VBoxContainer *popup_vbox = nullptr; + + void _popup_visibility_changed(bool p_visible); + +protected: + void _notification(int p_what); + +public: + virtual Size2 get_minimum_size() const override; + virtual void toggled(bool p_pressed) override; + + VBoxContainer *get_popup_hbox() const { return popup_vbox; } + + ControlEditorPopupButton(); +}; + +class ControlEditorPresetPicker : public MarginContainer { + GDCLASS(ControlEditorPresetPicker, MarginContainer); + + virtual void _preset_button_pressed(const int p_preset) {} + +protected: + static constexpr int grid_separation = 0; + HashMap<int, Button *> preset_buttons; + + void _add_row_button(HBoxContainer *p_row, const int p_preset, const String &p_name); + void _add_separator(BoxContainer *p_box, Separator *p_separator); + +public: + ControlEditorPresetPicker() {} +}; + +class AnchorPresetPicker : public ControlEditorPresetPicker { + GDCLASS(AnchorPresetPicker, ControlEditorPresetPicker); + + virtual void _preset_button_pressed(const int p_preset) override; + +protected: + void _notification(int p_notification); + static void _bind_methods(); + +public: + AnchorPresetPicker(); +}; + +class SizeFlagPresetPicker : public ControlEditorPresetPicker { + GDCLASS(SizeFlagPresetPicker, ControlEditorPresetPicker); + + CheckBox *expand_button = nullptr; + + bool vertical = false; + + virtual void _preset_button_pressed(const int p_preset) override; + +protected: + void _notification(int p_notification); + static void _bind_methods(); + +public: + void set_allowed_flags(Vector<SizeFlags> &p_flags); + + SizeFlagPresetPicker(bool p_vertical); +}; + +class ControlEditorToolbar : public HBoxContainer { + GDCLASS(ControlEditorToolbar, HBoxContainer); + + EditorSelection *editor_selection = nullptr; + + ControlEditorPopupButton *anchors_button = nullptr; + ControlEditorPopupButton *containers_button = nullptr; + Button *anchor_mode_button = nullptr; + + SizeFlagPresetPicker *container_h_picker = nullptr; + SizeFlagPresetPicker *container_v_picker = nullptr; + + bool anchors_mode = false; + + void _anchors_preset_selected(int p_preset); + void _anchors_to_current_ratio(); + void _anchor_mode_toggled(bool p_status); + void _container_flags_selected(int p_flags, bool p_vertical); + + Vector2 _position_to_anchor(const Control *p_control, Vector2 position); + bool _is_node_locked(const Node *p_node); + List<Control *> _get_edited_controls(); + void _selection_changed(); + +protected: + void _notification(int p_notification); + + static ControlEditorToolbar *singleton; + +public: + bool is_anchors_mode_enabled() { return anchors_mode; }; + + static ControlEditorToolbar *get_singleton() { return singleton; } + + ControlEditorToolbar(); +}; + +// Editor plugin. +class ControlEditorPlugin : public EditorPlugin { + GDCLASS(ControlEditorPlugin, EditorPlugin); + + ControlEditorToolbar *toolbar = nullptr; + +public: + virtual String get_name() const override { return "Control"; } + + ControlEditorPlugin(); +}; + +#endif // CONTROL_EDITOR_PLUGIN_H diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp index e0364dc952..85897742fb 100644 --- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_2d_editor_plugin.cpp @@ -1,40 +1,46 @@ -/*************************************************************************/ -/* cpu_particles_2d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* cpu_particles_2d_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 "cpu_particles_2d_editor_plugin.h" #include "canvas_item_editor_plugin.h" #include "core/io/image_loader.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "scene/2d/cpu_particles_2d.h" +#include "scene/gui/check_box.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/option_button.h" #include "scene/gui/separator.h" -#include "scene/resources/particles_material.h" +#include "scene/gui/spin_box.h" +#include "scene/resources/particle_process_material.h" void CPUParticles2DEditorPlugin::edit(Object *p_object) { particles = Object::cast_to<CPUParticles2D>(p_object); @@ -107,8 +113,8 @@ void CPUParticles2DEditorPlugin::_generate_emission_mask() { int vpc = 0; { - Vector<uint8_t> data = img->get_data(); - const uint8_t *r = data.ptr(); + Vector<uint8_t> img_data = img->get_data(); + const uint8_t *r = img_data.ptr(); for (int i = 0; i < s.width; i++) { for (int j = 0; j < s.height; j++) { @@ -222,20 +228,20 @@ void CPUParticles2DEditorPlugin::_generate_emission_mask() { } void CPUParticles2DEditorPlugin::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - menu->get_popup()->connect("id_pressed", callable_mp(this, &CPUParticles2DEditorPlugin::_menu_callback)); - menu->set_icon(epoints->get_theme_icon(SNAME("CPUParticles2D"), SNAME("EditorIcons"))); - file->connect("file_selected", callable_mp(this, &CPUParticles2DEditorPlugin::_file_selected)); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + menu->get_popup()->connect("id_pressed", callable_mp(this, &CPUParticles2DEditorPlugin::_menu_callback)); + menu->set_icon(epoints->get_theme_icon(SNAME("CPUParticles2D"), SNAME("EditorIcons"))); + file->connect("file_selected", callable_mp(this, &CPUParticles2DEditorPlugin::_file_selected)); + } break; } } void CPUParticles2DEditorPlugin::_bind_methods() { } -CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin(EditorNode *p_node) { +CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() { particles = nullptr; - editor = p_node; - undo_redo = editor->get_undo_redo(); toolbar = memnew(HBoxContainer); add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar); @@ -254,7 +260,7 @@ CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin(EditorNode *p_node) { List<String> ext; ImageLoader::get_recognized_extensions(&ext); for (const String &E : ext) { - file->add_filter("*." + E + "; " + E.to_upper()); + file->add_filter("*." + E, E.to_upper()); } file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); toolbar->add_child(file); diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.h b/editor/plugins/cpu_particles_2d_editor_plugin.h index e54e1651bd..cab9fca4d6 100644 --- a/editor/plugins/cpu_particles_2d_editor_plugin.h +++ b/editor/plugins/cpu_particles_2d_editor_plugin.h @@ -1,42 +1,47 @@ -/*************************************************************************/ -/* cpu_particles_2d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* cpu_particles_2d_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 CPU_PARTICLES_2D_EDITOR_PLUGIN_H #define CPU_PARTICLES_2D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/2d/collision_polygon_2d.h" #include "scene/2d/cpu_particles_2d.h" #include "scene/gui/box_container.h" -#include "scene/gui/file_dialog.h" + +class CheckBox; +class ConfirmationDialog; +class SpinBox; +class EditorFileDialog; +class MenuButton; +class OptionButton; class CPUParticles2DEditorPlugin : public EditorPlugin { GDCLASS(CPUParticles2DEditorPlugin, EditorPlugin); @@ -53,23 +58,21 @@ class CPUParticles2DEditorPlugin : public EditorPlugin { EMISSION_MODE_BORDER_DIRECTED }; - CPUParticles2D *particles; + CPUParticles2D *particles = nullptr; - EditorFileDialog *file; - EditorNode *editor; + EditorFileDialog *file = nullptr; - HBoxContainer *toolbar; - MenuButton *menu; + HBoxContainer *toolbar = nullptr; + MenuButton *menu = nullptr; - SpinBox *epoints; + SpinBox *epoints = nullptr; - ConfirmationDialog *emission_mask; - OptionButton *emission_mask_mode; - CheckBox *emission_colors; + ConfirmationDialog *emission_mask = nullptr; + OptionButton *emission_mask_mode = nullptr; + CheckBox *emission_colors = nullptr; String source_emission_file; - UndoRedo *undo_redo; void _file_selected(const String &p_file); void _menu_callback(int p_idx); void _generate_emission_mask(); @@ -85,7 +88,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - CPUParticles2DEditorPlugin(EditorNode *p_node); + CPUParticles2DEditorPlugin(); ~CPUParticles2DEditorPlugin(); }; diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.cpp b/editor/plugins/cpu_particles_3d_editor_plugin.cpp index bb10c04e8f..61702493da 100644 --- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_3d_editor_plugin.cpp @@ -1,36 +1,39 @@ -/*************************************************************************/ -/* cpu_particles_3d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* cpu_particles_3d_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 "cpu_particles_3d_editor_plugin.h" +#include "editor/editor_node.h" #include "editor/plugins/node_3d_editor_plugin.h" +#include "editor/scene_tree_editor.h" +#include "scene/gui/menu_button.h" void CPUParticles3DEditor::_node_removed(Node *p_node) { if (p_node == node) { @@ -40,8 +43,10 @@ void CPUParticles3DEditor::_node_removed(Node *p_node) { } void CPUParticles3DEditor::_notification(int p_notification) { - if (p_notification == NOTIFICATION_ENTER_TREE) { - options->set_icon(get_theme_icon(SNAME("CPUParticles3D"), SNAME("EditorIcons"))); + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: { + options->set_icon(get_theme_icon(SNAME("CPUParticles3D"), SNAME("EditorIcons"))); + } break; } } @@ -119,10 +124,9 @@ void CPUParticles3DEditorPlugin::make_visible(bool p_visible) { } } -CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() { particles_editor = memnew(CPUParticles3DEditor); - editor->get_main_control()->add_child(particles_editor); + EditorNode::get_singleton()->get_main_screen_control()->add_child(particles_editor); particles_editor->hide(); } diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.h b/editor/plugins/cpu_particles_3d_editor_plugin.h index 67cc156680..894e0dfb31 100644 --- a/editor/plugins/cpu_particles_3d_editor_plugin.h +++ b/editor/plugins/cpu_particles_3d_editor_plugin.h @@ -1,35 +1,35 @@ -/*************************************************************************/ -/* cpu_particles_3d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 CPU_PARTICLES_EDITOR_PLUGIN_H -#define CPU_PARTICLES_EDITOR_PLUGIN_H +/**************************************************************************/ +/* cpu_particles_3d_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 CPU_PARTICLES_3D_EDITOR_PLUGIN_H +#define CPU_PARTICLES_3D_EDITOR_PLUGIN_H #include "editor/plugins/gpu_particles_3d_editor_plugin.h" #include "scene/3d/cpu_particles_3d.h" @@ -44,7 +44,7 @@ class CPUParticles3DEditor : public GPUParticles3DEditorBase { }; - CPUParticles3D *node; + CPUParticles3D *node = nullptr; void _menu_option(int); @@ -65,8 +65,7 @@ public: class CPUParticles3DEditorPlugin : public EditorPlugin { GDCLASS(CPUParticles3DEditorPlugin, EditorPlugin); - CPUParticles3DEditor *particles_editor; - EditorNode *editor; + CPUParticles3DEditor *particles_editor = nullptr; public: virtual String get_name() const override { return "CPUParticles3D"; } @@ -75,8 +74,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - CPUParticles3DEditorPlugin(EditorNode *p_node); + CPUParticles3DEditorPlugin(); ~CPUParticles3DEditorPlugin(); }; -#endif // CPU_PARTICLES_EDITOR_PLUGIN_H +#endif // CPU_PARTICLES_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index 81fb36276f..20710bac19 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* curve_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* curve_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 "curve_editor_plugin.h" @@ -34,7 +34,10 @@ #include "core/core_string_names.h" #include "core/input/input.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" CurveEditor::CurveEditor() { _selected_point = -1; @@ -85,7 +88,7 @@ void CurveEditor::set_curve(Ref<Curve> curve) { _hover_point = -1; _selected_tangent = TANGENT_NONE; - update(); + queue_redraw(); // Note: if you edit a curve, then set another, and try to undo, // it will normally apply on the previous curve, but you won't see it @@ -96,8 +99,10 @@ Size2 CurveEditor::get_minimum_size() const { } void CurveEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - _draw(); + switch (p_what) { + case NOTIFICATION_DRAW: { + _draw(); + } break; } } @@ -135,14 +140,13 @@ void CurveEditor::gui_input(const Ref<InputEvent> &p_event) { if (!mb.is_pressed() && _dragging && mb.get_button_index() == MouseButton::LEFT) { _dragging = false; if (_has_undo_data) { - UndoRedo &ur = *EditorNode::get_singleton()->get_undo_redo(); - - ur.create_action(_selected_tangent == TANGENT_NONE ? TTR("Modify Curve Point") : TTR("Modify Curve Tangent")); - ur.add_do_method(*_curve_ref, "_set_data", _curve_ref->get_data()); - ur.add_undo_method(*_curve_ref, "_set_data", _undo_data); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(_selected_tangent == TANGENT_NONE ? TTR("Modify Curve Point") : TTR("Modify Curve Tangent")); + undo_redo->add_do_method(*_curve_ref, "_set_data", _curve_ref->get_data()); + undo_redo->add_undo_method(*_curve_ref, "_set_data", _undo_data); // Note: this will trigger one more "changed" signal even if nothing changes, // but it's ok since it would have fired every frame during the drag anyways - ur.commit_action(); + undo_redo->commit_action(); _has_undo_data = false; } @@ -297,17 +301,15 @@ void CurveEditor::on_preset_item_selected(int preset_id) { break; } - UndoRedo &ur = *EditorNode::get_singleton()->get_undo_redo(); - ur.create_action(TTR("Load Curve Preset")); - - ur.add_do_method(&curve, "_set_data", curve.get_data()); - ur.add_undo_method(&curve, "_set_data", previous_data); - - ur.commit_action(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Load Curve Preset")); + undo_redo->add_do_method(&curve, "_set_data", curve.get_data()); + undo_redo->add_undo_method(&curve, "_set_data", previous_data); + undo_redo->commit_action(); } void CurveEditor::_curve_changed() { - update(); + queue_redraw(); // Point count can change in case of undo if (_selected_point >= _curve_ref->get_point_count()) { set_selected_point(-1); @@ -431,8 +433,8 @@ CurveEditor::TangentIndex CurveEditor::get_tangent_at(Vector2 pos) const { void CurveEditor::add_point(Vector2 pos) { ERR_FAIL_COND(_curve_ref.is_null()); - UndoRedo &ur = *EditorNode::get_singleton()->get_undo_redo(); - ur.create_action(TTR("Remove Curve Point")); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Remove Curve Point")); Vector2 point_pos = get_world_pos(pos); if (point_pos.y < 0.0) { @@ -445,22 +447,21 @@ void CurveEditor::add_point(Vector2 pos) { int i = _curve_ref->add_point(point_pos); _curve_ref->remove_point(i); - ur.add_do_method(*_curve_ref, "add_point", point_pos); - ur.add_undo_method(*_curve_ref, "remove_point", i); - - ur.commit_action(); + undo_redo->add_do_method(*_curve_ref, "add_point", point_pos); + undo_redo->add_undo_method(*_curve_ref, "remove_point", i); + undo_redo->commit_action(); } void CurveEditor::remove_point(int index) { ERR_FAIL_COND(_curve_ref.is_null()); - UndoRedo &ur = *EditorNode::get_singleton()->get_undo_redo(); - ur.create_action(TTR("Remove Curve Point")); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Remove Curve Point")); Curve::Point p = _curve_ref->get_point(index); - ur.add_do_method(*_curve_ref, "remove_point", index); - ur.add_undo_method(*_curve_ref, "add_point", p.position, p.left_tangent, p.right_tangent, p.left_mode, p.right_mode); + undo_redo->add_do_method(*_curve_ref, "remove_point", index); + undo_redo->add_undo_method(*_curve_ref, "add_point", p.position, p.left_tangent, p.right_tangent, p.left_mode, p.right_mode); if (index == _selected_point) { set_selected_point(-1); @@ -470,14 +471,14 @@ void CurveEditor::remove_point(int index) { set_hover_point_index(-1); } - ur.commit_action(); + undo_redo->commit_action(); } void CurveEditor::toggle_linear(TangentIndex tangent) { ERR_FAIL_COND(_curve_ref.is_null()); - UndoRedo &ur = *EditorNode::get_singleton()->get_undo_redo(); - ur.create_action(TTR("Toggle Curve Linear Tangent")); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Toggle Curve Linear Tangent")); if (tangent == TANGENT_NONE) { tangent = _selected_tangent; @@ -489,8 +490,8 @@ void CurveEditor::toggle_linear(TangentIndex tangent) { Curve::TangentMode prev_mode = _curve_ref->get_point_left_mode(_selected_point); Curve::TangentMode mode = is_linear ? Curve::TANGENT_FREE : Curve::TANGENT_LINEAR; - ur.add_do_method(*_curve_ref, "set_point_left_mode", _selected_point, mode); - ur.add_undo_method(*_curve_ref, "set_point_left_mode", _selected_point, prev_mode); + undo_redo->add_do_method(*_curve_ref, "set_point_left_mode", _selected_point, mode); + undo_redo->add_undo_method(*_curve_ref, "set_point_left_mode", _selected_point, prev_mode); } else { bool is_linear = _curve_ref->get_point_right_mode(_selected_point) == Curve::TANGENT_LINEAR; @@ -498,24 +499,24 @@ void CurveEditor::toggle_linear(TangentIndex tangent) { Curve::TangentMode prev_mode = _curve_ref->get_point_right_mode(_selected_point); Curve::TangentMode mode = is_linear ? Curve::TANGENT_FREE : Curve::TANGENT_LINEAR; - ur.add_do_method(*_curve_ref, "set_point_right_mode", _selected_point, mode); - ur.add_undo_method(*_curve_ref, "set_point_right_mode", _selected_point, prev_mode); + undo_redo->add_do_method(*_curve_ref, "set_point_right_mode", _selected_point, mode); + undo_redo->add_undo_method(*_curve_ref, "set_point_right_mode", _selected_point, prev_mode); } - ur.commit_action(); + undo_redo->commit_action(); } void CurveEditor::set_selected_point(int index) { if (index != _selected_point) { _selected_point = index; - update(); + queue_redraw(); } } void CurveEditor::set_hover_point_index(int index) { if (index != _hover_point) { _hover_point = index; - update(); + queue_redraw(); } } @@ -539,11 +540,11 @@ void CurveEditor::update_view_transform() { const Vector2 scale = view_size / world_rect.size; Transform2D world_trans; - world_trans.translate(-world_rect.position - Vector2(0, world_rect.size.y)); + world_trans.translate_local(-world_rect.position - Vector2(0, world_rect.size.y)); world_trans.scale(Vector2(scale.x, -scale.y)); Transform2D view_trans; - view_trans.translate(view_margin); + view_trans.translate_local(view_margin); _world_to_view = view_trans * world_trans; } @@ -575,7 +576,7 @@ template <typename T> static void plot_curve_accurate(const Curve &curve, float step, T plot_func) { if (curve.get_point_count() <= 1) { // Not enough points to make a curve, so it's just a straight line - float y = curve.interpolate(0); + float y = curve.sample(0); plot_func(Vector2(0, y), Vector2(1.f, y), true); } else { @@ -599,7 +600,7 @@ static void plot_curve_accurate(const Curve &curve, float step, T plot_func) { for (float x = step; x < len; x += step) { pos.x = a.x + x; - pos.y = curve.interpolate_local_nocheck(i - 1, x); + pos.y = curve.sample_local_nocheck(i - 1, x); plot_func(prev_pos, pos, true); prev_pos = pos; } @@ -620,8 +621,8 @@ struct CanvasItemPlotCurve { color2(p_color2) {} void operator()(Vector2 pos0, Vector2 pos1, bool in_definition) { - // FIXME: Using a line width greater than 1 breaks curve rendering - ci.draw_line(pos0, pos1, in_definition ? color1 : color2, 1); + // FIXME: Using a quad line breaks curve rendering. + ci.draw_line(pos0, pos1, in_definition ? color1 : color2, -1); } }; @@ -636,7 +637,7 @@ void CurveEditor::_draw() { // Background Vector2 view_size = get_rect().size; - draw_style_box(get_theme_stylebox(SNAME("bg"), SNAME("Tree")), Rect2(Point2(), view_size)); + draw_style_box(get_theme_stylebox(SNAME("panel"), SNAME("Tree")), Rect2(Point2(), view_size)); // Grid @@ -748,12 +749,13 @@ void CurveEditor::_draw() { // Help text + float width = view_size.x - 60 * EDSCALE; if (_selected_point > 0 && _selected_point + 1 < curve.get_point_count()) { text_color.a *= 0.4; - draw_string(font, Vector2(50 * EDSCALE, font_height), TTR("Hold Shift to edit tangents individually"), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); + draw_multiline_string(font, Vector2(50 * EDSCALE, font_height), TTR("Hold Shift to edit tangents individually"), HORIZONTAL_ALIGNMENT_LEFT, width, font_size, -1, text_color); } else if (curve.get_point_count() == 0) { text_color.a *= 0.4; - draw_string(font, Vector2(50 * EDSCALE, font_height), TTR("Right click to add point"), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); + draw_multiline_string(font, Vector2(50 * EDSCALE, font_height), TTR("Right click to add point"), HORIZONTAL_ALIGNMENT_LEFT, width, font_size, -1, text_color); } } @@ -773,7 +775,7 @@ void EditorInspectorPluginCurve::parse_begin(Object *p_object) { add_custom_control(editor); } -CurveEditorPlugin::CurveEditorPlugin(EditorNode *p_node) { +CurveEditorPlugin::CurveEditorPlugin() { Ref<EditorInspectorPluginCurve> curve_plugin; curve_plugin.instantiate(); EditorInspector::add_inspector_plugin(curve_plugin); @@ -794,20 +796,17 @@ Ref<Texture2D> CurvePreviewGenerator::generate(const Ref<Resource> &p_from, cons Curve &curve = **curve_ref; // FIXME: Should be ported to use p_size as done in b2633a97 - int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size"); + int thumbnail_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size"); thumbnail_size *= EDSCALE; Ref<Image> img_ref; img_ref.instantiate(); Image &im = **img_ref; - im.create(thumbnail_size, thumbnail_size / 2, false, Image::FORMAT_RGBA8); + im.initialize_data(thumbnail_size, thumbnail_size / 2, false, Image::FORMAT_RGBA8); Color bg_color(0.1, 0.1, 0.1, 1.0); - for (int i = 0; i < thumbnail_size; i++) { - for (int j = 0; j < thumbnail_size / 2; j++) { - im.set_pixel(i, j, bg_color); - } - } + + im.fill(bg_color); Color line_color(0.8, 0.8, 0.8, 1.0); float range_y = curve.get_max_value() - curve.get_min_value(); @@ -815,7 +814,7 @@ Ref<Texture2D> CurvePreviewGenerator::generate(const Ref<Resource> &p_from, cons int prev_y = 0; for (int x = 0; x < im.get_width(); ++x) { float t = static_cast<float>(x) / im.get_width(); - float v = (curve.interpolate_baked(t) - curve.get_min_value()) / range_y; + float v = (curve.sample_baked(t) - curve.get_min_value()) / range_y; int y = CLAMP(im.get_height() - v * im.get_height(), 0, im.get_height()); // Plot point @@ -840,9 +839,5 @@ Ref<Texture2D> CurvePreviewGenerator::generate(const Ref<Resource> &p_from, cons prev_y = y; } - - Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); - - ptex->create_from_image(img_ref); - return ptex; + return ImageTexture::create_from_image(img_ref); } diff --git a/editor/plugins/curve_editor_plugin.h b/editor/plugins/curve_editor_plugin.h index c7e8dea75a..d5cc0cb66a 100644 --- a/editor/plugins/curve_editor_plugin.h +++ b/editor/plugins/curve_editor_plugin.h @@ -1,37 +1,37 @@ -/*************************************************************************/ -/* curve_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* curve_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 CURVE_EDITOR_PLUGIN_H #define CURVE_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/editor_inspector.h" #include "editor/editor_plugin.h" #include "editor/editor_resource_preview.h" #include "scene/resources/curve.h" @@ -100,8 +100,8 @@ private: Transform2D _world_to_view; Ref<Curve> _curve_ref; - PopupMenu *_context_menu; - PopupMenu *_presets_menu; + PopupMenu *_context_menu = nullptr; + PopupMenu *_presets_menu = nullptr; Array _undo_data; bool _has_undo_data; @@ -129,7 +129,7 @@ class CurveEditorPlugin : public EditorPlugin { GDCLASS(CurveEditorPlugin, EditorPlugin); public: - CurveEditorPlugin(EditorNode *p_node); + CurveEditorPlugin(); virtual String get_name() const override { return "Curve"; } }; diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index 6e43130a92..224d221d9a 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* debugger_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* debugger_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 "debugger_editor_plugin.h" @@ -35,17 +35,18 @@ #include "editor/debugger/editor_debugger_server.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "editor/fileserver/editor_file_server.h" +#include "editor/plugins/script_editor_plugin.h" #include "scene/gui/menu_button.h" -DebuggerEditorPlugin::DebuggerEditorPlugin(EditorNode *p_editor, MenuButton *p_debug_menu) { +DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) { EditorDebuggerServer::initialize(); ED_SHORTCUT("debugger/step_into", TTR("Step Into"), Key::F11); ED_SHORTCUT("debugger/step_over", TTR("Step Over"), Key::F10); ED_SHORTCUT("debugger/break", TTR("Break")); ED_SHORTCUT("debugger/continue", TTR("Continue"), Key::F12); - ED_SHORTCUT("debugger/keep_debugger_open", TTR("Keep Debugger Open")); ED_SHORTCUT("debugger/debug_with_external_editor", TTR("Debug with External Editor")); // File Server for deploy with remote filesystem. @@ -54,60 +55,55 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(EditorNode *p_editor, MenuButton *p_d EditorDebuggerNode *debugger = memnew(EditorDebuggerNode); Button *db = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Debugger"), debugger); // Add separation for the warning/error icon that is displayed later. - db->add_theme_constant_override("hseparation", 6 * EDSCALE); + db->add_theme_constant_override("h_separation", 6 * EDSCALE); debugger->set_tool_button(db); // Main editor debug menu. debug_menu = p_debug_menu; - PopupMenu *p = debug_menu->get_popup(); - p->set_hide_on_checkable_item_selection(false); - p->add_check_shortcut(ED_SHORTCUT("editor/deploy_with_remote_debug", TTR("Deploy with Remote Debug")), RUN_DEPLOY_REMOTE_DEBUG); - p->set_item_tooltip( - p->get_item_count() - 1, + debug_menu->set_hide_on_checkable_item_selection(false); + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/deploy_with_remote_debug", TTR("Deploy with Remote Debug")), RUN_DEPLOY_REMOTE_DEBUG); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, using one-click deploy will make the executable attempt to connect to this computer's IP so the running project can be debugged.\nThis option is intended to be used for remote debugging (typically with a mobile device).\nYou don't need to enable it to use the GDScript debugger locally.")); - p->add_check_shortcut(ED_SHORTCUT("editor/small_deploy_with_network_fs", TTR("Small Deploy with Network Filesystem")), RUN_FILE_SERVER); - p->set_item_tooltip( - p->get_item_count() - 1, + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/small_deploy_with_network_fs", TTR("Small Deploy with Network Filesystem")), RUN_FILE_SERVER); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, using one-click deploy for Android will only export an executable without the project data.\nThe filesystem will be provided from the project by the editor over the network.\nOn Android, deploying will use the USB cable for faster performance. This option speeds up testing for projects with large assets.")); - p->add_separator(); - p->add_check_shortcut(ED_SHORTCUT("editor/visible_collision_shapes", TTR("Visible Collision Shapes")), RUN_DEBUG_COLLISONS); - p->set_item_tooltip( - p->get_item_count() - 1, + debug_menu->add_separator(); + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_collision_shapes", TTR("Visible Collision Shapes")), RUN_DEBUG_COLLISIONS); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, collision shapes and raycast nodes (for 2D and 3D) will be visible in the running project.")); - p->add_check_shortcut(ED_SHORTCUT("editor/visible_navigation", TTR("Visible Navigation")), RUN_DEBUG_NAVIGATION); - p->set_item_tooltip( - p->get_item_count() - 1, + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_paths", TTR("Visible Paths")), RUN_DEBUG_PATHS); + debug_menu->set_item_tooltip(-1, + TTR("When this option is enabled, curve resources used by path nodes will be visible in the running project.")); + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_navigation", TTR("Visible Navigation")), RUN_DEBUG_NAVIGATION); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, navigation meshes and polygons will be visible in the running project.")); - p->add_separator(); - p->add_check_shortcut(ED_SHORTCUT("editor/sync_scene_changes", TTR("Synchronize Scene Changes")), RUN_LIVE_DEBUG); - p->set_item_tooltip( - p->get_item_count() - 1, + debug_menu->add_separator(); + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/sync_scene_changes", TTR("Synchronize Scene Changes")), RUN_LIVE_DEBUG); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, any changes made to the scene in the editor will be replicated in the running project.\nWhen used remotely on a device, this is more efficient when the network filesystem option is enabled.")); - p->add_check_shortcut(ED_SHORTCUT("editor/sync_script_changes", TTR("Synchronize Script Changes")), RUN_RELOAD_SCRIPTS); - p->set_item_tooltip( - p->get_item_count() - 1, + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/sync_script_changes", TTR("Synchronize Script Changes")), RUN_RELOAD_SCRIPTS); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, any script that is saved will be reloaded in the running project.\nWhen used remotely on a device, this is more efficient when the network filesystem option is enabled.")); + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/keep_server_open", TTR("Keep Debug Server Open")), SERVER_KEEP_OPEN); + debug_menu->set_item_tooltip(-1, + TTR("When this option is enabled, the editor debug server will stay open and listen for new sessions started outside of the editor itself.")); // Multi-instance, start/stop instances_menu = memnew(PopupMenu); instances_menu->set_name("run_instances"); instances_menu->set_hide_on_checkable_item_selection(false); - p->add_child(instances_menu); - p->add_separator(); - p->add_submenu_item(TTR("Run Multiple Instances"), "run_instances"); - - instances_menu->add_radio_check_item(TTR("Run 1 Instance")); - instances_menu->set_item_metadata(0, 1); - instances_menu->add_radio_check_item(TTR("Run 2 Instances")); - instances_menu->set_item_metadata(1, 2); - instances_menu->add_radio_check_item(TTR("Run 3 Instances")); - instances_menu->set_item_metadata(2, 3); - instances_menu->add_radio_check_item(TTR("Run 4 Instances")); - instances_menu->set_item_metadata(3, 4); + debug_menu->add_child(instances_menu); + debug_menu->add_separator(); + debug_menu->add_submenu_item(TTR("Run Multiple Instances"), "run_instances"); + + for (int i = 1; i <= 4; i++) { + instances_menu->add_radio_check_item(vformat(TTRN("Run %d Instance", "Run %d Instances", i), i)); + instances_menu->set_item_metadata(i - 1, i); + } instances_menu->set_item_checked(0, true); instances_menu->connect("index_pressed", callable_mp(this, &DebuggerEditorPlugin::_select_run_count)); - p->connect("id_pressed", callable_mp(this, &DebuggerEditorPlugin::_menu_option)); + debug_menu->connect("id_pressed", callable_mp(this, &DebuggerEditorPlugin::_menu_option)); } DebuggerEditorPlugin::~DebuggerEditorPlugin() { @@ -126,7 +122,7 @@ void DebuggerEditorPlugin::_select_run_count(int p_index) { void DebuggerEditorPlugin::_menu_option(int p_option) { switch (p_option) { case RUN_FILE_SERVER: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_FILE_SERVER)); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_FILE_SERVER)); if (ischecked) { file_server->stop(); @@ -134,60 +130,78 @@ void DebuggerEditorPlugin::_menu_option(int p_option) { file_server->start(); } - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_FILE_SERVER), !ischecked); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_FILE_SERVER), !ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_file_server", !ischecked); } break; case RUN_LIVE_DEBUG: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_LIVE_DEBUG)); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_LIVE_DEBUG)); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_LIVE_DEBUG), !ischecked); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_LIVE_DEBUG), !ischecked); EditorDebuggerNode::get_singleton()->set_live_debugging(!ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_live_debug", !ischecked); } break; case RUN_DEPLOY_REMOTE_DEBUG: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEPLOY_REMOTE_DEBUG)); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEPLOY_REMOTE_DEBUG), !ischecked); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEPLOY_REMOTE_DEBUG)); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEPLOY_REMOTE_DEBUG), !ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_deploy_remote_debug", !ischecked); } break; - case RUN_DEBUG_COLLISONS: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_COLLISONS)); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_COLLISONS), !ischecked); - EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_collisons", !ischecked); + case RUN_DEBUG_COLLISIONS: { + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEBUG_COLLISIONS)); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEBUG_COLLISIONS), !ischecked); + EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_collisions", !ischecked); + + } break; + case RUN_DEBUG_PATHS: { + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEBUG_PATHS)); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEBUG_PATHS), !ischecked); + EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_paths", !ischecked); } break; case RUN_DEBUG_NAVIGATION: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_NAVIGATION)); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_NAVIGATION), !ischecked); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEBUG_NAVIGATION)); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEBUG_NAVIGATION), !ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_navigation", !ischecked); } break; case RUN_RELOAD_SCRIPTS: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_RELOAD_SCRIPTS)); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_RELOAD_SCRIPTS), !ischecked); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_RELOAD_SCRIPTS)); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_RELOAD_SCRIPTS), !ischecked); ScriptEditor::get_singleton()->set_live_auto_reload_running_scripts(!ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_reload_scripts", !ischecked); } break; + case SERVER_KEEP_OPEN: { + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(SERVER_KEEP_OPEN)); + debug_menu->set_item_checked(debug_menu->get_item_index(SERVER_KEEP_OPEN), !ischecked); + + EditorDebuggerNode::get_singleton()->set_keep_open(!ischecked); + EditorSettings::get_singleton()->set_project_metadata("debug_options", "server_keep_open", !ischecked); + + } break; } } void DebuggerEditorPlugin::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - _update_debug_options(); + switch (p_what) { + case NOTIFICATION_READY: { + _update_debug_options(); + } break; } } void DebuggerEditorPlugin::_update_debug_options() { bool check_deploy_remote = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_deploy_remote_debug", false); bool check_file_server = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_file_server", false); - bool check_debug_collisions = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_collisons", false); + bool check_debug_collisions = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_collisions", false); + bool check_debug_paths = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_paths", false); bool check_debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false); bool check_live_debug = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_live_debug", true); bool check_reload_scripts = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_reload_scripts", true); + bool check_server_keep_open = EditorSettings::get_singleton()->get_project_metadata("debug_options", "server_keep_open", false); int instances = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_instances", 1); if (check_deploy_remote) { @@ -197,7 +211,10 @@ void DebuggerEditorPlugin::_update_debug_options() { _menu_option(RUN_FILE_SERVER); } if (check_debug_collisions) { - _menu_option(RUN_DEBUG_COLLISONS); + _menu_option(RUN_DEBUG_COLLISIONS); + } + if (check_debug_paths) { + _menu_option(RUN_DEBUG_PATHS); } if (check_debug_navigation) { _menu_option(RUN_DEBUG_NAVIGATION); @@ -208,6 +225,9 @@ void DebuggerEditorPlugin::_update_debug_options() { if (check_reload_scripts) { _menu_option(RUN_RELOAD_SCRIPTS); } + if (check_server_keep_open) { + _menu_option(SERVER_KEEP_OPEN); + } int len = instances_menu->get_item_count(); for (int idx = 0; idx < len; idx++) { diff --git a/editor/plugins/debugger_editor_plugin.h b/editor/plugins/debugger_editor_plugin.h index 6fc83cd438..6e2d2c02ee 100644 --- a/editor/plugins/debugger_editor_plugin.h +++ b/editor/plugins/debugger_editor_plugin.h @@ -1,39 +1,38 @@ -/*************************************************************************/ -/* debugger_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* debugger_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 DEBUGGER_EDITOR_PLUGIN_H #define DEBUGGER_EDITOR_PLUGIN_H #include "editor/editor_plugin.h" -class EditorNode; class EditorFileServer; class MenuButton; class PopupMenu; @@ -42,17 +41,19 @@ class DebuggerEditorPlugin : public EditorPlugin { GDCLASS(DebuggerEditorPlugin, EditorPlugin); private: - MenuButton *debug_menu; - EditorFileServer *file_server; - PopupMenu *instances_menu; + PopupMenu *debug_menu = nullptr; + EditorFileServer *file_server = nullptr; + PopupMenu *instances_menu = nullptr; enum MenuOptions { RUN_FILE_SERVER, RUN_LIVE_DEBUG, - RUN_DEBUG_COLLISONS, + RUN_DEBUG_COLLISIONS, + RUN_DEBUG_PATHS, RUN_DEBUG_NAVIGATION, RUN_DEPLOY_REMOTE_DEBUG, RUN_RELOAD_SCRIPTS, + SERVER_KEEP_OPEN, }; void _update_debug_options(); @@ -64,7 +65,7 @@ public: virtual String get_name() const override { return "Debugger"; } bool has_main_screen() const override { return false; } - DebuggerEditorPlugin(EditorNode *p_node, MenuButton *p_menu); + DebuggerEditorPlugin(PopupMenu *p_menu); ~DebuggerEditorPlugin(); }; diff --git a/editor/plugins/dedicated_server_export_plugin.cpp b/editor/plugins/dedicated_server_export_plugin.cpp new file mode 100644 index 0000000000..013706034e --- /dev/null +++ b/editor/plugins/dedicated_server_export_plugin.cpp @@ -0,0 +1,139 @@ +/**************************************************************************/ +/* dedicated_server_export_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 "dedicated_server_export_plugin.h" + +EditorExportPreset::FileExportMode DedicatedServerExportPlugin::_get_export_mode_for_path(const String &p_path) { + Ref<EditorExportPreset> preset = get_export_preset(); + ERR_FAIL_COND_V(preset.is_null(), EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED); + + EditorExportPreset::FileExportMode mode = preset->get_file_export_mode(p_path); + if (mode != EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED) { + return mode; + } + + String path = p_path; + if (path.begins_with("res://")) { + path = path.substr(6); + } + + Vector<String> parts = path.split("/"); + + while (parts.size() > 0) { + parts.resize(parts.size() - 1); + + String test_path = "res://"; + if (parts.size() > 0) { + test_path += String("/").join(parts) + "/"; + } + + mode = preset->get_file_export_mode(test_path); + if (mode != EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED) { + break; + } + } + + return mode; +} + +PackedStringArray DedicatedServerExportPlugin::_get_export_features(const Ref<EditorExportPlatform> &p_platform, bool p_debug) const { + PackedStringArray ret; + + Ref<EditorExportPreset> preset = get_export_preset(); + ERR_FAIL_COND_V(preset.is_null(), ret); + + if (preset->is_dedicated_server()) { + ret.append("dedicated_server"); + } + return ret; +} + +uint64_t DedicatedServerExportPlugin::_get_customization_configuration_hash() const { + Ref<EditorExportPreset> preset = get_export_preset(); + ERR_FAIL_COND_V(preset.is_null(), 0); + + if (preset->get_export_filter() != EditorExportPreset::EXPORT_CUSTOMIZED) { + return 0; + } + + return preset->get_customized_files().hash(); +} + +bool DedicatedServerExportPlugin::_begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) { + Ref<EditorExportPreset> preset = get_export_preset(); + ERR_FAIL_COND_V(preset.is_null(), false); + + current_export_mode = EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED; + + return preset->get_export_filter() == EditorExportPreset::EXPORT_CUSTOMIZED; +} + +bool DedicatedServerExportPlugin::_begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) { + Ref<EditorExportPreset> preset = get_export_preset(); + ERR_FAIL_COND_V(preset.is_null(), false); + + current_export_mode = EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED; + + return preset->get_export_filter() == EditorExportPreset::EXPORT_CUSTOMIZED; +} + +Node *DedicatedServerExportPlugin::_customize_scene(Node *p_root, const String &p_path) { + // Simply set the export mode based on the scene path. All the real + // customization happens in _customize_resource(). + current_export_mode = _get_export_mode_for_path(p_path); + return nullptr; +} + +Ref<Resource> DedicatedServerExportPlugin::_customize_resource(const Ref<Resource> &p_resource, const String &p_path) { + // If the resource has a path, we use that to get our export mode. But if it + // doesn't, we assume that this resource is embedded in the last resource with + // a path. + if (p_path != "") { + current_export_mode = _get_export_mode_for_path(p_path); + } + + if (p_resource.is_valid() && current_export_mode == EditorExportPreset::MODE_FILE_STRIP && p_resource->has_method("create_placeholder")) { + Callable::CallError err; + Ref<Resource> result = const_cast<Resource *>(p_resource.ptr())->callp("create_placeholder", nullptr, 0, err); + if (err.error == Callable::CallError::CALL_OK) { + return result; + } + } + + return Ref<Resource>(); +} + +void DedicatedServerExportPlugin::_end_customize_scenes() { + current_export_mode = EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED; +} + +void DedicatedServerExportPlugin::_end_customize_resources() { + current_export_mode = EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED; +} diff --git a/editor/plugins/dedicated_server_export_plugin.h b/editor/plugins/dedicated_server_export_plugin.h new file mode 100644 index 0000000000..cb014ae52d --- /dev/null +++ b/editor/plugins/dedicated_server_export_plugin.h @@ -0,0 +1,58 @@ +/**************************************************************************/ +/* dedicated_server_export_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 DEDICATED_SERVER_EXPORT_PLUGIN_H +#define DEDICATED_SERVER_EXPORT_PLUGIN_H + +#include "editor/export/editor_export.h" + +class DedicatedServerExportPlugin : public EditorExportPlugin { +private: + EditorExportPreset::FileExportMode current_export_mode; + + EditorExportPreset::FileExportMode _get_export_mode_for_path(const String &p_path); + +protected: + String _get_name() const override { return "DedicatedServer"; } + + PackedStringArray _get_export_features(const Ref<EditorExportPlatform> &p_platform, bool p_debug) const override; + uint64_t _get_customization_configuration_hash() const override; + + bool _begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) override; + bool _begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) override; + + Node *_customize_scene(Node *p_root, const String &p_path) override; + Ref<Resource> _customize_resource(const Ref<Resource> &p_resource, const String &p_path) override; + + void _end_customize_scenes() override; + void _end_customize_resources() override; +}; + +#endif // DEDICATED_SERVER_EXPORT_PLUGIN_H diff --git a/editor/plugins/editor_debugger_plugin.cpp b/editor/plugins/editor_debugger_plugin.cpp index 4ce3d7cfd5..eb1b2dcca7 100644 --- a/editor/plugins/editor_debugger_plugin.cpp +++ b/editor/plugins/editor_debugger_plugin.cpp @@ -1,38 +1,38 @@ -/*************************************************************************/ -/* editor_debugger_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* editor_debugger_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 "editor_debugger_plugin.h" #include "editor/debugger/script_editor_debugger.h" -void EditorDebuggerPlugin::_breaked(bool p_really_did, bool p_can_debug, String p_message, bool p_has_stackdump) { +void EditorDebuggerSession::_breaked(bool p_really_did, bool p_can_debug, String p_message, bool p_has_stackdump) { if (p_really_did) { emit_signal(SNAME("breaked"), p_can_debug); } else { @@ -40,22 +40,22 @@ void EditorDebuggerPlugin::_breaked(bool p_really_did, bool p_can_debug, String } } -void EditorDebuggerPlugin::_started() { +void EditorDebuggerSession::_started() { emit_signal(SNAME("started")); } -void EditorDebuggerPlugin::_stopped() { +void EditorDebuggerSession::_stopped() { emit_signal(SNAME("stopped")); } -void EditorDebuggerPlugin::_bind_methods() { - ClassDB::bind_method(D_METHOD("send_message", "message", "data"), &EditorDebuggerPlugin::send_message); - ClassDB::bind_method(D_METHOD("register_message_capture", "name", "callable"), &EditorDebuggerPlugin::register_message_capture); - ClassDB::bind_method(D_METHOD("unregister_message_capture", "name"), &EditorDebuggerPlugin::unregister_message_capture); - ClassDB::bind_method(D_METHOD("has_capture", "name"), &EditorDebuggerPlugin::has_capture); - ClassDB::bind_method(D_METHOD("is_breaked"), &EditorDebuggerPlugin::is_breaked); - ClassDB::bind_method(D_METHOD("is_debuggable"), &EditorDebuggerPlugin::is_debuggable); - ClassDB::bind_method(D_METHOD("is_session_active"), &EditorDebuggerPlugin::is_session_active); +void EditorDebuggerSession::_bind_methods() { + ClassDB::bind_method(D_METHOD("send_message", "message", "data"), &EditorDebuggerSession::send_message, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("toggle_profiler", "profiler", "enable", "data"), &EditorDebuggerSession::toggle_profiler, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("is_breaked"), &EditorDebuggerSession::is_breaked); + ClassDB::bind_method(D_METHOD("is_debuggable"), &EditorDebuggerSession::is_debuggable); + ClassDB::bind_method(D_METHOD("is_active"), &EditorDebuggerSession::is_active); + ClassDB::bind_method(D_METHOD("add_session_tab", "control"), &EditorDebuggerSession::add_session_tab); + ClassDB::bind_method(D_METHOD("remove_session_tab", "control"), &EditorDebuggerSession::remove_session_tab); ADD_SIGNAL(MethodInfo("started")); ADD_SIGNAL(MethodInfo("stopped")); @@ -63,62 +63,131 @@ void EditorDebuggerPlugin::_bind_methods() { ADD_SIGNAL(MethodInfo("continued")); } -void EditorDebuggerPlugin::attach_debugger(ScriptEditorDebugger *p_debugger) { - debugger = p_debugger; - if (debugger) { - debugger->connect("started", callable_mp(this, &EditorDebuggerPlugin::_started)); - debugger->connect("stopped", callable_mp(this, &EditorDebuggerPlugin::_stopped)); - debugger->connect("breaked", callable_mp(this, &EditorDebuggerPlugin::_breaked)); - } +void EditorDebuggerSession::add_session_tab(Control *p_tab) { + ERR_FAIL_COND(!p_tab || !debugger); + debugger->add_debugger_tab(p_tab); + tabs.insert(p_tab); } -void EditorDebuggerPlugin::detach_debugger(bool p_call_debugger) { - if (debugger) { - debugger->disconnect("started", callable_mp(this, &EditorDebuggerPlugin::_started)); - debugger->disconnect("stopped", callable_mp(this, &EditorDebuggerPlugin::_stopped)); - debugger->disconnect("breaked", callable_mp(this, &EditorDebuggerPlugin::_breaked)); - if (p_call_debugger && get_script_instance()) { - debugger->remove_debugger_plugin(get_script_instance()->get_script()); - } - debugger = nullptr; - } +void EditorDebuggerSession::remove_session_tab(Control *p_tab) { + ERR_FAIL_COND(!p_tab || !debugger); + debugger->remove_debugger_tab(p_tab); + tabs.erase(p_tab); } -void EditorDebuggerPlugin::send_message(const String &p_message, const Array &p_args) { +void EditorDebuggerSession::send_message(const String &p_message, const Array &p_args) { ERR_FAIL_COND_MSG(!debugger, "Plugin is not attached to debugger"); debugger->send_message(p_message, p_args); } -void EditorDebuggerPlugin::register_message_capture(const StringName &p_name, const Callable &p_callable) { +void EditorDebuggerSession::toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data) { ERR_FAIL_COND_MSG(!debugger, "Plugin is not attached to debugger"); - debugger->register_message_capture(p_name, p_callable); + debugger->toggle_profiler(p_profiler, p_enable, p_data); } -void EditorDebuggerPlugin::unregister_message_capture(const StringName &p_name) { - ERR_FAIL_COND_MSG(!debugger, "Plugin is not attached to debugger"); - debugger->unregister_message_capture(p_name); -} - -bool EditorDebuggerPlugin::has_capture(const StringName &p_name) { - ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger"); - return debugger->has_capture(p_name); -} - -bool EditorDebuggerPlugin::is_breaked() { +bool EditorDebuggerSession::is_breaked() { ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger"); return debugger->is_breaked(); } -bool EditorDebuggerPlugin::is_debuggable() { +bool EditorDebuggerSession::is_debuggable() { ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger"); return debugger->is_debuggable(); } -bool EditorDebuggerPlugin::is_session_active() { +bool EditorDebuggerSession::is_active() { ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger"); return debugger->is_session_active(); } +void EditorDebuggerSession::detach_debugger() { + if (!debugger) { + return; + } + debugger->disconnect("started", callable_mp(this, &EditorDebuggerSession::_started)); + debugger->disconnect("stopped", callable_mp(this, &EditorDebuggerSession::_stopped)); + debugger->disconnect("breaked", callable_mp(this, &EditorDebuggerSession::_breaked)); + debugger->disconnect("tree_exited", callable_mp(this, &EditorDebuggerSession::_debugger_gone_away)); + for (Control *tab : tabs) { + debugger->remove_debugger_tab(tab); + } + tabs.clear(); + debugger = nullptr; +} + +void EditorDebuggerSession::_debugger_gone_away() { + debugger = nullptr; + tabs.clear(); +} + +EditorDebuggerSession::EditorDebuggerSession(ScriptEditorDebugger *p_debugger) { + ERR_FAIL_COND(!p_debugger); + debugger = p_debugger; + debugger->connect("started", callable_mp(this, &EditorDebuggerSession::_started)); + debugger->connect("stopped", callable_mp(this, &EditorDebuggerSession::_stopped)); + debugger->connect("breaked", callable_mp(this, &EditorDebuggerSession::_breaked)); + debugger->connect("tree_exited", callable_mp(this, &EditorDebuggerSession::_debugger_gone_away), CONNECT_ONE_SHOT); +} + +EditorDebuggerSession::~EditorDebuggerSession() { + detach_debugger(); +} + +/// EditorDebuggerPlugin + EditorDebuggerPlugin::~EditorDebuggerPlugin() { - detach_debugger(true); + clear(); +} + +void EditorDebuggerPlugin::clear() { + for (int i = 0; i < sessions.size(); i++) { + sessions[i]->detach_debugger(); + } + sessions.clear(); +} + +void EditorDebuggerPlugin::create_session(ScriptEditorDebugger *p_debugger) { + sessions.push_back(Ref<EditorDebuggerSession>(memnew(EditorDebuggerSession(p_debugger)))); + setup_session(sessions.size() - 1); +} + +void EditorDebuggerPlugin::setup_session(int p_idx) { + GDVIRTUAL_CALL(_setup_session, p_idx); +} + +Ref<EditorDebuggerSession> EditorDebuggerPlugin::get_session(int p_idx) { + ERR_FAIL_INDEX_V(p_idx, sessions.size(), nullptr); + return sessions[p_idx]; +} + +Array EditorDebuggerPlugin::get_sessions() { + Array ret; + for (int i = 0; i < sessions.size(); i++) { + ret.push_back(sessions[i]); + } + return ret; +} + +bool EditorDebuggerPlugin::has_capture(const String &p_message) const { + bool ret = false; + if (GDVIRTUAL_CALL(_has_capture, p_message, ret)) { + return ret; + } + return false; +} + +bool EditorDebuggerPlugin::capture(const String &p_message, const Array &p_data, int p_session_id) { + bool ret = false; + if (GDVIRTUAL_CALL(_capture, p_message, p_data, p_session_id, ret)) { + return ret; + } + return false; +} + +void EditorDebuggerPlugin::_bind_methods() { + GDVIRTUAL_BIND(_setup_session, "session_id"); + GDVIRTUAL_BIND(_has_capture, "capture"); + GDVIRTUAL_BIND(_capture, "message", "data", "session_id"); + ClassDB::bind_method(D_METHOD("get_session", "id"), &EditorDebuggerPlugin::get_session); + ClassDB::bind_method(D_METHOD("get_sessions"), &EditorDebuggerPlugin::get_sessions); } diff --git a/editor/plugins/editor_debugger_plugin.h b/editor/plugins/editor_debugger_plugin.h index b602c36912..10c0e29f6e 100644 --- a/editor/plugins/editor_debugger_plugin.h +++ b/editor/plugins/editor_debugger_plugin.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* editor_debugger_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* editor_debugger_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 EDITOR_DEBUGGER_PLUGIN_H #define EDITOR_DEBUGGER_PLUGIN_H @@ -35,29 +35,62 @@ class ScriptEditorDebugger; -class EditorDebuggerPlugin : public Control { - GDCLASS(EditorDebuggerPlugin, Control); +class EditorDebuggerSession : public RefCounted { + GDCLASS(EditorDebuggerSession, RefCounted); private: + HashSet<Control *> tabs; + ScriptEditorDebugger *debugger = nullptr; void _breaked(bool p_really_did, bool p_can_debug, String p_message, bool p_has_stackdump); void _started(); void _stopped(); + void _debugger_gone_away(); protected: static void _bind_methods(); public: - void attach_debugger(ScriptEditorDebugger *p_debugger); - void detach_debugger(bool p_call_debugger); - void send_message(const String &p_message, const Array &p_args); - void register_message_capture(const StringName &p_name, const Callable &p_callable); - void unregister_message_capture(const StringName &p_name); - bool has_capture(const StringName &p_name); + void detach_debugger(); + + void add_session_tab(Control *p_tab); + void remove_session_tab(Control *p_tab); + void send_message(const String &p_message, const Array &p_args = Array()); + void toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data = Array()); bool is_breaked(); bool is_debuggable(); - bool is_session_active(); + bool is_active(); + + EditorDebuggerSession(ScriptEditorDebugger *p_debugger); + ~EditorDebuggerSession(); +}; + +class EditorDebuggerPlugin : public RefCounted { + GDCLASS(EditorDebuggerPlugin, RefCounted); + +private: + List<Ref<EditorDebuggerSession>> sessions; + +protected: + static void _bind_methods(); + +public: + void create_session(ScriptEditorDebugger *p_debugger); + void clear(); + + virtual void setup_session(int p_idx); + virtual bool capture(const String &p_message, const Array &p_data, int p_session); + virtual bool has_capture(const String &p_capture) const; + + Ref<EditorDebuggerSession> get_session(int p_session_id); + Array get_sessions(); + + GDVIRTUAL3R(bool, _capture, const String &, const Array &, int); + GDVIRTUAL1RC(bool, _has_capture, const String &); + GDVIRTUAL1(_setup_session, int); + + EditorDebuggerPlugin() {} ~EditorDebuggerPlugin(); }; diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 2053194dc1..523e7703c4 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -1,39 +1,40 @@ -/*************************************************************************/ -/* editor_preview_plugins.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* editor_preview_plugins.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 "editor_preview_plugins.h" +#include "core/config/project_settings.h" #include "core/io/file_access_memory.h" #include "core/io/resource_loader.h" #include "core/os/os.h" -#include "editor/editor_node.h" +#include "editor/editor_paths.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "scene/resources/bit_map.h" @@ -78,7 +79,7 @@ bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const { return true; } -Ref<Texture2D> EditorTexturePreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<Image> img; Ref<AtlasTexture> atex = p_from; if (atex.is_valid()) { @@ -92,7 +93,7 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const RES &p_from, const Siz return Ref<Texture2D>(); } - img = atlas->get_rect(atex->get_region()); + img = atlas->get_region(atex->get_region()); } else { Ref<Texture2D> tex = p_from; if (tex.is_valid()) { @@ -126,13 +127,9 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const RES &p_from, const Siz } Vector2i new_size_i(MAX(1, (int)new_size.x), MAX(1, (int)new_size.y)); img->resize(new_size_i.x, new_size_i.y, Image::INTERPOLATE_CUBIC); - post_process_preview(img); - Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); - - ptex->create_from_image(img); - return ptex; + return ImageTexture::create_from_image(img); } EditorTexturePreviewPlugin::EditorTexturePreviewPlugin() { @@ -144,7 +141,7 @@ bool EditorImagePreviewPlugin::handles(const String &p_type) const { return p_type == "Image"; } -Ref<Texture2D> EditorImagePreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorImagePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<Image> img = p_from; if (img.is_null() || img->is_empty()) { @@ -170,14 +167,9 @@ Ref<Texture2D> EditorImagePreviewPlugin::generate(const RES &p_from, const Size2 new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y); } img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC); - post_process_preview(img); - Ref<ImageTexture> ptex; - ptex.instantiate(); - - ptex->create_from_image(img); - return ptex; + return ImageTexture::create_from_image(img); } EditorImagePreviewPlugin::EditorImagePreviewPlugin() { @@ -188,12 +180,12 @@ bool EditorImagePreviewPlugin::generate_small_preview_automatically() const { } //////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////// + bool EditorBitmapPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "BitMap"); } -Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<BitMap> bm = p_from; if (bm->get_size() == Size2()) { @@ -209,7 +201,7 @@ Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const RES &p_from, const Size for (int i = 0; i < bm->get_size().width; i++) { for (int j = 0; j < bm->get_size().height; j++) { - if (bm->get_bit(Point2i(i, j))) { + if (bm->get_bit(i, j)) { w[j * (int)bm->get_size().width + i] = 255; } else { w[j * (int)bm->get_size().width + i] = 0; @@ -218,9 +210,7 @@ Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const RES &p_from, const Size } } - Ref<Image> img; - img.instantiate(); - img->create(bm->get_size().width, bm->get_size().height, false, Image::FORMAT_L8, data); + Ref<Image> img = Image::create_from_data(bm->get_size().width, bm->get_size().height, false, Image::FORMAT_L8, data); if (img->is_compressed()) { if (img->decompress() != OK) { @@ -238,13 +228,9 @@ Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const RES &p_from, const Size new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y); } img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC); - post_process_preview(img); - Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); - - ptex->create_from_image(img); - return ptex; + return ImageTexture::create_from_image(img); } bool EditorBitmapPreviewPlugin::generate_small_preview_automatically() const { @@ -260,14 +246,14 @@ bool EditorPackedScenePreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "PackedScene"); } -Ref<Texture2D> EditorPackedScenePreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorPackedScenePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { return generate_from_path(p_from->get_path(), p_size); } Ref<Texture2D> EditorPackedScenePreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size) const { String temp_path = EditorPaths::get_singleton()->get_cache_dir(); String cache_base = ProjectSettings::get_singleton()->globalize_path(p_path).md5_text(); - cache_base = temp_path.plus_file("resthumb-" + cache_base); + cache_base = temp_path.path_join("resthumb-" + cache_base); //does not have it, try to load a cached thumbnail @@ -281,11 +267,8 @@ Ref<Texture2D> EditorPackedScenePreviewPlugin::generate_from_path(const String & img.instantiate(); Error err = img->load(path); if (err == OK) { - Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); - post_process_preview(img); - ptex->create_from_image(img); - return ptex; + return ImageTexture::create_from_image(img); } else { return Ref<Texture2D>(); @@ -308,21 +291,21 @@ void EditorMaterialPreviewPlugin::_preview_done() { } bool EditorMaterialPreviewPlugin::handles(const String &p_type) const { - return ClassDB::is_parent_class(p_type, "Material"); //any material + return ClassDB::is_parent_class(p_type, "Material"); // Any material. } bool EditorMaterialPreviewPlugin::generate_small_preview_automatically() const { return true; } -Ref<Texture2D> EditorMaterialPreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorMaterialPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<Material> material = p_from; ERR_FAIL_COND_V(material.is_null(), Ref<Texture2D>()); if (material->get_shader_mode() == Shader::MODE_SPATIAL) { RS::get_singleton()->mesh_surface_set_material(sphere, 0, material->get_rid()); - RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorMaterialPreviewPlugin *>(this), &EditorMaterialPreviewPlugin::_generate_frame_started), Vector<Variant>(), Object::CONNECT_ONESHOT); + RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorMaterialPreviewPlugin *>(this), &EditorMaterialPreviewPlugin::_generate_frame_started), Object::CONNECT_ONE_SHOT); preview_done.wait(); @@ -335,9 +318,7 @@ Ref<Texture2D> EditorMaterialPreviewPlugin::generate(const RES &p_from, const Si int thumbnail_size = MAX(p_size.x, p_size.y); img->resize(thumbnail_size, thumbnail_size, Image::INTERPOLATE_CUBIC); post_process_preview(img); - Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); - ptex->create_from_image(img); - return ptex; + return ImageTexture::create_from_image(img); } return Ref<Texture2D>(); @@ -359,6 +340,12 @@ EditorMaterialPreviewPlugin::EditorMaterialPreviewPlugin() { RS::get_singleton()->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 0, 3))); RS::get_singleton()->camera_set_perspective(camera, 45, 0.1, 10); + if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { + camera_attributes = RS::get_singleton()->camera_attributes_create(); + RS::get_singleton()->camera_attributes_set_exposure(camera_attributes, 1.0, 0.000032552); // Matches default CameraAttributesPhysical to work well with default DirectionalLight3Ds. + RS::get_singleton()->camera_set_camera_attributes(camera, camera_attributes); + } + light = RS::get_singleton()->directional_light_create(); light_instance = RS::get_singleton()->instance_create2(light, scenario); RS::get_singleton()->instance_set_transform(light_instance, Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0))); @@ -449,6 +436,7 @@ EditorMaterialPreviewPlugin::EditorMaterialPreviewPlugin() { } EditorMaterialPreviewPlugin::~EditorMaterialPreviewPlugin() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); RS::get_singleton()->free(sphere); RS::get_singleton()->free(sphere_instance); RS::get_singleton()->free(viewport); @@ -457,20 +445,17 @@ EditorMaterialPreviewPlugin::~EditorMaterialPreviewPlugin() { RS::get_singleton()->free(light2); RS::get_singleton()->free(light_instance2); RS::get_singleton()->free(camera); + RS::get_singleton()->free(camera_attributes); RS::get_singleton()->free(scenario); } /////////////////////////////////////////////////////////////////////////// -static bool _is_text_char(char32_t c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; -} - bool EditorScriptPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "Script"); } -Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorScriptPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<Script> scr = p_from; if (scr.is_null()) { return Ref<Texture2D>(); @@ -484,8 +469,8 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size List<String> kwors; scr->get_language()->get_reserved_words(&kwors); - Set<String> control_flow_keywords; - Set<String> keywords; + HashSet<String> control_flow_keywords; + HashSet<String> keywords; for (const String &E : kwors) { if (scr->get_language()->is_control_flow_keyword(E)) { @@ -497,28 +482,22 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size int line = 0; int col = 0; - Ref<Image> img; - img.instantiate(); int thumbnail_size = MAX(p_size.x, p_size.y); - img->create(thumbnail_size, thumbnail_size, false, Image::FORMAT_RGBA8); + Ref<Image> img = Image::create_empty(thumbnail_size, thumbnail_size, false, Image::FORMAT_RGBA8); - Color bg_color = EditorSettings::get_singleton()->get("text_editor/theme/highlighting/background_color"); - Color keyword_color = EditorSettings::get_singleton()->get("text_editor/theme/highlighting/keyword_color"); - Color control_flow_keyword_color = EditorSettings::get_singleton()->get("text_editor/theme/highlighting/control_flow_keyword_color"); - Color text_color = EditorSettings::get_singleton()->get("text_editor/theme/highlighting/text_color"); - Color symbol_color = EditorSettings::get_singleton()->get("text_editor/theme/highlighting/symbol_color"); - Color comment_color = EditorSettings::get_singleton()->get("text_editor/theme/highlighting/comment_color"); + Color bg_color = EDITOR_GET("text_editor/theme/highlighting/background_color"); + Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); + Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); + Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color"); + Color symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color"); + Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); if (bg_color.a == 0) { bg_color = Color(0, 0, 0, 0); } bg_color.a = MAX(bg_color.a, 0.2); // some background - for (int i = 0; i < thumbnail_size; i++) { - for (int j = 0; j < thumbnail_size; j++) { - img->set_pixel(i, j, bg_color); - } - } + img->fill(bg_color); const int x0 = thumbnail_size / 8; const int y0 = thumbnail_size / 8; @@ -542,15 +521,15 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size if (in_comment) { color = comment_color; } else { - if (c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t')) { + if (is_symbol(c)) { //make symbol a little visible color = symbol_color; in_control_flow_keyword = false; in_keyword = false; - } else if (!prev_is_text && _is_text_char(c)) { + } else if (!prev_is_text && is_ascii_identifier_char(c)) { int pos = i; - while (_is_text_char(code[pos])) { + while (is_ascii_identifier_char(code[pos])) { pos++; } String word = code.substr(i, pos - i); @@ -560,7 +539,7 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size in_keyword = true; } - } else if (!_is_text_char(c)) { + } else if (!is_ascii_identifier_char(c)) { in_keyword = false; } @@ -575,7 +554,7 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size img->set_pixel(col, y0 + line * 2, bg_color.blend(ul)); img->set_pixel(col, y0 + line * 2 + 1, color); - prev_is_text = _is_text_char(c); + prev_is_text = is_ascii_identifier_char(c); } col++; } else { @@ -598,13 +577,8 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size } } } - post_process_preview(img); - - Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); - - ptex->create_from_image(img); - return ptex; + return ImageTexture::create_from_image(img); } EditorScriptPreviewPlugin::EditorScriptPreviewPlugin() { @@ -616,7 +590,7 @@ bool EditorAudioStreamPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "AudioStream"); } -Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<AudioStream> stream = p_from; ERR_FAIL_COND_V(stream.is_null(), Ref<Texture2D>()); @@ -629,7 +603,7 @@ Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const RES &p_from, const uint8_t *imgdata = img.ptrw(); uint8_t *imgw = imgdata; - Ref<AudioStreamPlayback> playback = stream->instance_playback(); + Ref<AudioStreamPlayback> playback = stream->instantiate_playback(); ERR_FAIL_COND_V(playback.is_null(), Ref<Texture2D>()); real_t len_s = stream->get_length(); @@ -683,12 +657,8 @@ Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const RES &p_from, const //post_process_preview(img); - Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); - Ref<Image> image; - image.instantiate(); - image->create(w, h, false, Image::FORMAT_RGB8, img); - ptex->create_from_image(image); - return ptex; + Ref<Image> image = Image::create_from_data(w, h, false, Image::FORMAT_RGB8, img); + return ImageTexture::create_from_image(image); } EditorAudioStreamPreviewPlugin::EditorAudioStreamPreviewPlugin() { @@ -707,10 +677,10 @@ void EditorMeshPreviewPlugin::_preview_done() { } bool EditorMeshPreviewPlugin::handles(const String &p_type) const { - return ClassDB::is_parent_class(p_type, "Mesh"); //any Mesh + return ClassDB::is_parent_class(p_type, "Mesh"); // Any mesh. } -Ref<Texture2D> EditorMeshPreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorMeshPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<Mesh> mesh = p_from; ERR_FAIL_COND_V(mesh.is_null(), Ref<Texture2D>()); @@ -734,7 +704,7 @@ Ref<Texture2D> EditorMeshPreviewPlugin::generate(const RES &p_from, const Size2 xform.origin.z -= rot_aabb.size.z * 2; RS::get_singleton()->instance_set_transform(mesh_instance, xform); - RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorMeshPreviewPlugin *>(this), &EditorMeshPreviewPlugin::_generate_frame_started), Vector<Variant>(), Object::CONNECT_ONESHOT); + RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorMeshPreviewPlugin *>(this), &EditorMeshPreviewPlugin::_generate_frame_started), Object::CONNECT_ONE_SHOT); preview_done.wait(); @@ -753,12 +723,9 @@ Ref<Texture2D> EditorMeshPreviewPlugin::generate(const RES &p_from, const Size2 new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y); } img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC); - post_process_preview(img); - Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); - ptex->create_from_image(img); - return ptex; + return ImageTexture::create_from_image(img); } EditorMeshPreviewPlugin::EditorMeshPreviewPlugin() { @@ -778,6 +745,12 @@ EditorMeshPreviewPlugin::EditorMeshPreviewPlugin() { //RS::get_singleton()->camera_set_perspective(camera,45,0.1,10); RS::get_singleton()->camera_set_orthogonal(camera, 1.0, 0.01, 1000.0); + if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { + camera_attributes = RS::get_singleton()->camera_attributes_create(); + RS::get_singleton()->camera_attributes_set_exposure(camera_attributes, 1.0, 0.000032552); // Matches default CameraAttributesPhysical to work well with default DirectionalLight3Ds. + RS::get_singleton()->camera_set_camera_attributes(camera, camera_attributes); + } + light = RS::get_singleton()->directional_light_create(); light_instance = RS::get_singleton()->instance_create2(light, scenario); RS::get_singleton()->instance_set_transform(light_instance, Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0))); @@ -795,6 +768,7 @@ EditorMeshPreviewPlugin::EditorMeshPreviewPlugin() { } EditorMeshPreviewPlugin::~EditorMeshPreviewPlugin() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); //RS::get_singleton()->free(sphere); RS::get_singleton()->free(mesh_instance); RS::get_singleton()->free(viewport); @@ -803,6 +777,7 @@ EditorMeshPreviewPlugin::~EditorMeshPreviewPlugin() { RS::get_singleton()->free(light2); RS::get_singleton()->free(light_instance2); RS::get_singleton()->free(camera); + RS::get_singleton()->free(camera_attributes); RS::get_singleton()->free(scenario); } @@ -819,19 +794,12 @@ void EditorFontPreviewPlugin::_preview_done() { } bool EditorFontPreviewPlugin::handles(const String &p_type) const { - return ClassDB::is_parent_class(p_type, "FontData") || ClassDB::is_parent_class(p_type, "Font"); + return ClassDB::is_parent_class(p_type, "Font"); } Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size) const { - RES res = ResourceLoader::load(p_path); - ERR_FAIL_COND_V(res.is_null(), Ref<Texture2D>()); - Ref<Font> sampled_font; - if (res->is_class("Font")) { - sampled_font = res->duplicate(); - } else if (res->is_class("FontData")) { - sampled_font.instantiate(); - sampled_font->add_data(res->duplicate()); - } + Ref<Font> sampled_font = ResourceLoader::load(p_path); + ERR_FAIL_COND_V(sampled_font.is_null(), Ref<Texture2D>()); String sample; static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀"; @@ -843,20 +811,18 @@ Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, if (sample.is_empty()) { sample = sampled_font->get_supported_chars().substr(0, 6); } - Vector2 size = sampled_font->get_string_size(sample, 50); + Vector2 size = sampled_font->get_string_size(sample, HORIZONTAL_ALIGNMENT_LEFT, -1, 50); Vector2 pos; pos.x = 64 - size.x / 2; pos.y = 80; - Ref<Font> font = sampled_font; - const Color c = GLOBAL_GET("rendering/environment/defaults/default_clear_color"); const float fg = c.get_luminance() < 0.5 ? 1.0 : 0.0; - font->draw_string(canvas_item, pos, sample, HORIZONTAL_ALIGNMENT_LEFT, -1.f, 50, Color(fg, fg, fg)); + sampled_font->draw_string(canvas_item, pos, sample, HORIZONTAL_ALIGNMENT_LEFT, -1.f, 50, Color(fg, fg, fg)); - RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorFontPreviewPlugin *>(this), &EditorFontPreviewPlugin::_generate_frame_started), Vector<Variant>(), Object::CONNECT_ONESHOT); + RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorFontPreviewPlugin *>(this), &EditorFontPreviewPlugin::_generate_frame_started), Object::CONNECT_ONE_SHOT); preview_done.wait(); @@ -875,16 +841,12 @@ Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y); } img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC); - post_process_preview(img); - Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); - ptex->create_from_image(img); - - return ptex; + return ImageTexture::create_from_image(img); } -Ref<Texture2D> EditorFontPreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorFontPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { String path = p_from->get_path(); if (!FileAccess::exists(path)) { return Ref<Texture2D>(); @@ -907,7 +869,35 @@ EditorFontPreviewPlugin::EditorFontPreviewPlugin() { } EditorFontPreviewPlugin::~EditorFontPreviewPlugin() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); RS::get_singleton()->free(canvas_item); RS::get_singleton()->free(canvas); RS::get_singleton()->free(viewport); } + +//////////////////////////////////////////////////////////////////////////// + +static const real_t GRADIENT_PREVIEW_TEXTURE_SCALE_FACTOR = 4.0; + +bool EditorGradientPreviewPlugin::handles(const String &p_type) const { + return ClassDB::is_parent_class(p_type, "Gradient"); +} + +bool EditorGradientPreviewPlugin::generate_small_preview_automatically() const { + return true; +} + +Ref<Texture2D> EditorGradientPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { + Ref<Gradient> gradient = p_from; + if (gradient.is_valid()) { + Ref<GradientTexture1D> ptex; + ptex.instantiate(); + ptex->set_width(p_size.width * GRADIENT_PREVIEW_TEXTURE_SCALE_FACTOR * EDSCALE); + ptex->set_gradient(gradient); + return ImageTexture::create_from_image(ptex->get_image()); + } + return Ref<Texture2D>(); +} + +EditorGradientPreviewPlugin::EditorGradientPreviewPlugin() { +} diff --git a/editor/plugins/editor_preview_plugins.h b/editor/plugins/editor_preview_plugins.h index dd64918d41..6e4b73481c 100644 --- a/editor/plugins/editor_preview_plugins.h +++ b/editor/plugins/editor_preview_plugins.h @@ -1,39 +1,38 @@ -/*************************************************************************/ -/* editor_preview_plugins.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 EDITORPREVIEWPLUGINS_H -#define EDITORPREVIEWPLUGINS_H - -#include "editor/editor_resource_preview.h" +/**************************************************************************/ +/* editor_preview_plugins.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 EDITOR_PREVIEW_PLUGINS_H +#define EDITOR_PREVIEW_PLUGINS_H #include "core/templates/safe_refcount.h" +#include "editor/editor_resource_preview.h" void post_process_preview(Ref<Image> p_image); @@ -43,7 +42,7 @@ class EditorTexturePreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; virtual bool generate_small_preview_automatically() const override; - virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; EditorTexturePreviewPlugin(); }; @@ -54,7 +53,7 @@ class EditorImagePreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; virtual bool generate_small_preview_automatically() const override; - virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; EditorImagePreviewPlugin(); }; @@ -65,16 +64,18 @@ class EditorBitmapPreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; virtual bool generate_small_preview_automatically() const override; - virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; EditorBitmapPreviewPlugin(); }; class EditorPackedScenePreviewPlugin : public EditorResourcePreviewGenerator { + GDCLASS(EditorPackedScenePreviewPlugin, EditorResourcePreviewGenerator); + public: - virtual bool handles(const String &p_type) const; - virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const; - virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size) const; + virtual bool handles(const String &p_type) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size) const override; EditorPackedScenePreviewPlugin(); }; @@ -92,6 +93,7 @@ class EditorMaterialPreviewPlugin : public EditorResourcePreviewGenerator { RID light2; RID light_instance2; RID camera; + RID camera_attributes; Semaphore preview_done; void _generate_frame_started(); @@ -100,24 +102,28 @@ class EditorMaterialPreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; virtual bool generate_small_preview_automatically() const override; - virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; EditorMaterialPreviewPlugin(); ~EditorMaterialPreviewPlugin(); }; class EditorScriptPreviewPlugin : public EditorResourcePreviewGenerator { + GDCLASS(EditorScriptPreviewPlugin, EditorResourcePreviewGenerator); + public: - virtual bool handles(const String &p_type) const; - virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const; + virtual bool handles(const String &p_type) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; EditorScriptPreviewPlugin(); }; class EditorAudioStreamPreviewPlugin : public EditorResourcePreviewGenerator { + GDCLASS(EditorAudioStreamPreviewPlugin, EditorResourcePreviewGenerator); + public: - virtual bool handles(const String &p_type) const; - virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const; + virtual bool handles(const String &p_type) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; EditorAudioStreamPreviewPlugin(); }; @@ -134,6 +140,7 @@ class EditorMeshPreviewPlugin : public EditorResourcePreviewGenerator { RID light2; RID light_instance2; RID camera; + RID camera_attributes; Semaphore preview_done; void _generate_frame_started(); @@ -141,7 +148,7 @@ class EditorMeshPreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; - virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; EditorMeshPreviewPlugin(); ~EditorMeshPreviewPlugin(); @@ -161,7 +168,7 @@ class EditorFontPreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; - virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size) const override; EditorFontPreviewPlugin(); @@ -178,9 +185,21 @@ class EditorTileMapPatternPreviewPlugin : public EditorResourcePreviewGenerator public: virtual bool handles(const String &p_type) const override; - virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; EditorTileMapPatternPreviewPlugin(); ~EditorTileMapPatternPreviewPlugin(); }; -#endif // EDITORPREVIEWPLUGINS_H + +class EditorGradientPreviewPlugin : public EditorResourcePreviewGenerator { + GDCLASS(EditorGradientPreviewPlugin, EditorResourcePreviewGenerator); + +public: + virtual bool handles(const String &p_type) const override; + virtual bool generate_small_preview_automatically() const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + + EditorGradientPreviewPlugin(); +}; + +#endif // EDITOR_PREVIEW_PLUGINS_H diff --git a/editor/plugins/editor_resource_conversion_plugin.cpp b/editor/plugins/editor_resource_conversion_plugin.cpp new file mode 100644 index 0000000000..3209b576b2 --- /dev/null +++ b/editor/plugins/editor_resource_conversion_plugin.cpp @@ -0,0 +1,55 @@ +/**************************************************************************/ +/* editor_resource_conversion_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 "editor_resource_conversion_plugin.h" + +void EditorResourceConversionPlugin::_bind_methods() { + GDVIRTUAL_BIND(_converts_to); + GDVIRTUAL_BIND(_handles, "resource"); + GDVIRTUAL_BIND(_convert, "resource"); +} + +String EditorResourceConversionPlugin::converts_to() const { + String ret; + GDVIRTUAL_CALL(_converts_to, ret); + return ret; +} + +bool EditorResourceConversionPlugin::handles(const Ref<Resource> &p_resource) const { + bool ret = false; + GDVIRTUAL_CALL(_handles, p_resource, ret); + return ret; +} + +Ref<Resource> EditorResourceConversionPlugin::convert(const Ref<Resource> &p_resource) const { + Ref<Resource> ret; + GDVIRTUAL_CALL(_convert, p_resource, ret); + return ret; +} diff --git a/editor/plugins/editor_resource_conversion_plugin.h b/editor/plugins/editor_resource_conversion_plugin.h new file mode 100644 index 0000000000..32e05585ee --- /dev/null +++ b/editor/plugins/editor_resource_conversion_plugin.h @@ -0,0 +1,54 @@ +/**************************************************************************/ +/* editor_resource_conversion_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 EDITOR_RESOURCE_CONVERSION_PLUGIN_H +#define EDITOR_RESOURCE_CONVERSION_PLUGIN_H + +#include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" + +class EditorResourceConversionPlugin : public RefCounted { + GDCLASS(EditorResourceConversionPlugin, RefCounted); + +protected: + static void _bind_methods(); + + GDVIRTUAL0RC(String, _converts_to) + GDVIRTUAL1RC(bool, _handles, Ref<Resource>) + GDVIRTUAL1RC(Ref<Resource>, _convert, Ref<Resource>) + +public: + virtual String converts_to() const; + virtual bool handles(const Ref<Resource> &p_resource) const; + virtual Ref<Resource> convert(const Ref<Resource> &p_resource) const; +}; + +#endif // EDITOR_RESOURCE_CONVERSION_PLUGIN_H diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp new file mode 100644 index 0000000000..7618ec3903 --- /dev/null +++ b/editor/plugins/font_config_plugin.cpp @@ -0,0 +1,1065 @@ +/**************************************************************************/ +/* font_config_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 "font_config_plugin.h" + +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/import/dynamic_font_import_settings.h" + +/*************************************************************************/ +/* EditorPropertyFontMetaObject */ +/*************************************************************************/ + +bool EditorPropertyFontMetaObject::_set(const StringName &p_name, const Variant &p_value) { + String name = p_name; + + if (name.begins_with("keys")) { + String key = name.get_slicec('/', 1); + dict[key] = p_value; + return true; + } + + return false; +} + +bool EditorPropertyFontMetaObject::_get(const StringName &p_name, Variant &r_ret) const { + String name = p_name; + + if (name.begins_with("keys")) { + String key = name.get_slicec('/', 1); + r_ret = dict[key]; + return true; + } + + return false; +} + +void EditorPropertyFontMetaObject::_bind_methods() { +} + +void EditorPropertyFontMetaObject::set_dict(const Dictionary &p_dict) { + dict = p_dict; +} + +Dictionary EditorPropertyFontMetaObject::get_dict() { + return dict; +} + +/*************************************************************************/ +/* EditorPropertyFontOTObject */ +/*************************************************************************/ + +bool EditorPropertyFontOTObject::_set(const StringName &p_name, const Variant &p_value) { + String name = p_name; + + if (name.begins_with("keys")) { + int key = name.get_slicec('/', 1).to_int(); + dict[key] = p_value; + return true; + } + + return false; +} + +bool EditorPropertyFontOTObject::_get(const StringName &p_name, Variant &r_ret) const { + String name = p_name; + + if (name.begins_with("keys")) { + int key = name.get_slicec('/', 1).to_int(); + r_ret = dict[key]; + return true; + } + + return false; +} + +void EditorPropertyFontOTObject::set_dict(const Dictionary &p_dict) { + dict = p_dict; +} + +Dictionary EditorPropertyFontOTObject::get_dict() { + return dict; +} + +void EditorPropertyFontOTObject::set_defaults(const Dictionary &p_dict) { + defaults_dict = p_dict; +} + +Dictionary EditorPropertyFontOTObject::get_defaults() { + return defaults_dict; +} + +bool EditorPropertyFontOTObject::_property_can_revert(const StringName &p_name) const { + String name = p_name; + + if (name.begins_with("keys")) { + int key = name.get_slicec('/', 1).to_int(); + if (defaults_dict.has(key) && dict.has(key)) { + int value = dict[key]; + Vector3i range = defaults_dict[key]; + return range.z != value; + } + } + + return false; +} + +bool EditorPropertyFontOTObject::_property_get_revert(const StringName &p_name, Variant &r_property) const { + String name = p_name; + + if (name.begins_with("keys")) { + int key = name.get_slicec('/', 1).to_int(); + if (defaults_dict.has(key)) { + Vector3i range = defaults_dict[key]; + r_property = range.z; + return true; + } + } + + return false; +} + +/*************************************************************************/ +/* EditorPropertyFontMetaOverride */ +/*************************************************************************/ + +void EditorPropertyFontMetaOverride::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + if (button_add) { + button_add->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + } + } break; + } +} + +void EditorPropertyFontMetaOverride::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + if (p_property.begins_with("keys")) { + Dictionary dict = object->get_dict(); + String key = p_property.get_slice("/", 1); + dict[key] = (bool)p_value; + + emit_changed(get_edited_property(), dict, "", true); + + dict = dict.duplicate(); // Duplicate, so undo/redo works better. + object->set_dict(dict); + } +} + +void EditorPropertyFontMetaOverride::_remove(Object *p_button, const String &p_key) { + Dictionary dict = object->get_dict(); + + dict.erase(p_key); + + emit_changed(get_edited_property(), dict, "", false); + + dict = dict.duplicate(); // Duplicate, so undo/redo works better. + object->set_dict(dict); + update_property(); +} + +void EditorPropertyFontMetaOverride::_add_menu() { + if (script_editor) { + Size2 size = get_size(); + menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); + menu->reset_size(); + menu->popup(); + } else { + locale_select->popup_locale_dialog(); + } +} + +void EditorPropertyFontMetaOverride::_add_script(int p_option) { + Dictionary dict = object->get_dict(); + + dict[script_codes[p_option]] = true; + + emit_changed(get_edited_property(), dict, "", false); + + dict = dict.duplicate(); // Duplicate, so undo/redo works better. + object->set_dict(dict); + update_property(); +} + +void EditorPropertyFontMetaOverride::_add_lang(const String &p_locale) { + Dictionary dict = object->get_dict(); + + dict[p_locale] = true; + + emit_changed(get_edited_property(), dict, "", false); + + dict = dict.duplicate(); // Duplicate, so undo/redo works better. + object->set_dict(dict); + update_property(); +} + +void EditorPropertyFontMetaOverride::_object_id_selected(const StringName &p_property, ObjectID p_id) { + emit_signal(SNAME("object_id_selected"), p_property, p_id); +} + +void EditorPropertyFontMetaOverride::update_property() { + Variant updated_val = get_edited_object()->get(get_edited_property()); + + Dictionary dict = updated_val; + + edit->set_text(vformat(TTR("Overrides (%d)"), dict.size())); + + bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property()); + if (edit->is_pressed() != unfolded) { + edit->set_pressed(unfolded); + } + + if (unfolded) { + updating = true; + + if (!container) { + container = memnew(MarginContainer); + container->set_theme_type_variation("MarginContainer4px"); + add_child(container); + set_bottom_editor(container); + + VBoxContainer *vbox = memnew(VBoxContainer); + vbox->set_v_size_flags(SIZE_EXPAND_FILL); + container->add_child(vbox); + + property_vbox = memnew(VBoxContainer); + property_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + vbox->add_child(property_vbox); + + paginator = memnew(EditorPaginator); + paginator->connect("page_changed", callable_mp(this, &EditorPropertyFontMetaOverride::_page_changed)); + vbox->add_child(paginator); + } else { + // Queue children for deletion, deleting immediately might cause errors. + for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { + property_vbox->get_child(i)->queue_free(); + } + button_add = nullptr; + } + + int size = dict.size(); + + int max_page = MAX(0, size - 1) / page_length; + page_index = MIN(page_index, max_page); + + paginator->update(page_index, max_page); + paginator->set_visible(max_page > 0); + + int offset = page_index * page_length; + + int amount = MIN(size - offset, page_length); + + dict = dict.duplicate(); + object->set_dict(dict); + + for (int i = 0; i < amount; i++) { + String name = dict.get_key_at_index(i); + EditorProperty *prop = memnew(EditorPropertyCheck); + prop->set_object_and_property(object.ptr(), "keys/" + name); + + if (script_editor) { + prop->set_label(TranslationServer::get_singleton()->get_script_name(name)); + } else { + prop->set_label(TranslationServer::get_singleton()->get_locale_name(name)); + } + prop->set_tooltip_text(name); + prop->set_selectable(false); + + prop->connect("property_changed", callable_mp(this, &EditorPropertyFontMetaOverride::_property_changed)); + prop->connect("object_id_selected", callable_mp(this, &EditorPropertyFontMetaOverride::_object_id_selected)); + + HBoxContainer *hbox = memnew(HBoxContainer); + property_vbox->add_child(hbox); + hbox->add_child(prop); + prop->set_h_size_flags(SIZE_EXPAND_FILL); + Button *remove = memnew(Button); + remove->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + hbox->add_child(remove); + remove->connect("pressed", callable_mp(this, &EditorPropertyFontMetaOverride::_remove).bind(remove, name)); + + prop->update_property(); + } + + if (script_editor) { + // TRANSLATORS: Script refers to a writing system. + button_add = EditorInspector::create_inspector_action_button(TTR("Add Script", "Locale")); + } else { + button_add = EditorInspector::create_inspector_action_button(TTR("Add Locale")); + } + button_add->connect("pressed", callable_mp(this, &EditorPropertyFontMetaOverride::_add_menu)); + property_vbox->add_child(button_add); + + updating = false; + } else { + if (container) { + set_bottom_editor(nullptr); + memdelete(container); + button_add = nullptr; + container = nullptr; + } + } +} + +void EditorPropertyFontMetaOverride::_edit_pressed() { + Variant prop_val = get_edited_object()->get(get_edited_property()); + if (prop_val.get_type() == Variant::NIL) { + Callable::CallError ce; + Variant::construct(Variant::DICTIONARY, prop_val, nullptr, 0, ce); + get_edited_object()->set(get_edited_property(), prop_val); + } + + get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed()); + update_property(); +} + +void EditorPropertyFontMetaOverride::_page_changed(int p_page) { + if (updating) { + return; + } + page_index = p_page; + update_property(); +} + +EditorPropertyFontMetaOverride::EditorPropertyFontMetaOverride(bool p_script) { + script_editor = p_script; + + object.instantiate(); + page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page")); + + edit = memnew(Button); + edit->set_h_size_flags(SIZE_EXPAND_FILL); + edit->set_clip_text(true); + edit->connect("pressed", callable_mp(this, &EditorPropertyFontMetaOverride::_edit_pressed)); + edit->set_toggle_mode(true); + add_child(edit); + add_focusable(edit); + + menu = memnew(PopupMenu); + if (script_editor) { + script_codes = TranslationServer::get_singleton()->get_all_scripts(); + for (int i = 0; i < script_codes.size(); i++) { + menu->add_item(TranslationServer::get_singleton()->get_script_name(script_codes[i]) + " (" + script_codes[i] + ")", i); + } + } + add_child(menu); + menu->connect("id_pressed", callable_mp(this, &EditorPropertyFontMetaOverride::_add_script)); + + locale_select = memnew(EditorLocaleDialog); + locale_select->connect("locale_selected", callable_mp(this, &EditorPropertyFontMetaOverride::_add_lang)); + add_child(locale_select); +} + +/*************************************************************************/ +/* EditorPropertyOTVariation */ +/*************************************************************************/ + +void EditorPropertyOTVariation::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + } break; + } +} + +void EditorPropertyOTVariation::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + if (p_property.begins_with("keys")) { + Dictionary dict = object->get_dict(); + Dictionary defaults_dict = object->get_defaults(); + int key = p_property.get_slice("/", 1).to_int(); + dict[key] = (int)p_value; + if (defaults_dict.has(key)) { + Vector3i range = defaults_dict[key]; + if (range.z == (int)p_value) { + dict.erase(key); + } + } + + emit_changed(get_edited_property(), dict, "", true); + + dict = dict.duplicate(); // Duplicate, so undo/redo works better. + object->set_dict(dict); + } +} + +void EditorPropertyOTVariation::_object_id_selected(const StringName &p_property, ObjectID p_id) { + emit_signal(SNAME("object_id_selected"), p_property, p_id); +} + +void EditorPropertyOTVariation::update_property() { + Variant updated_val = get_edited_object()->get(get_edited_property()); + + Dictionary dict = updated_val; + + Ref<Font> fd; + if (Object::cast_to<Font>(get_edited_object()) != nullptr) { + fd = get_edited_object(); + } else if (Object::cast_to<DynamicFontImportSettingsData>(get_edited_object()) != nullptr) { + Ref<DynamicFontImportSettingsData> imp = Object::cast_to<DynamicFontImportSettingsData>(get_edited_object()); + fd = imp->get_font(); + } + + Dictionary supported = (fd.is_valid()) ? fd->get_supported_variation_list() : Dictionary(); + + edit->set_text(vformat(TTR("Variation Coordinates (%d)"), supported.size())); + + bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property()); + if (edit->is_pressed() != unfolded) { + edit->set_pressed(unfolded); + } + + if (unfolded) { + updating = true; + + if (!container) { + container = memnew(MarginContainer); + container->set_theme_type_variation("MarginContainer4px"); + add_child(container); + set_bottom_editor(container); + + VBoxContainer *vbox = memnew(VBoxContainer); + vbox->set_v_size_flags(SIZE_EXPAND_FILL); + container->add_child(vbox); + + property_vbox = memnew(VBoxContainer); + property_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + vbox->add_child(property_vbox); + + paginator = memnew(EditorPaginator); + paginator->connect("page_changed", callable_mp(this, &EditorPropertyOTVariation::_page_changed)); + vbox->add_child(paginator); + } else { + // Queue children for deletion, deleting immediately might cause errors. + for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { + property_vbox->get_child(i)->queue_free(); + } + } + + int size = supported.size(); + + int max_page = MAX(0, size - 1) / page_length; + page_index = MIN(page_index, max_page); + + paginator->update(page_index, max_page); + paginator->set_visible(max_page > 0); + + int offset = page_index * page_length; + + int amount = MIN(size - offset, page_length); + + dict = dict.duplicate(); + object->set_dict(dict); + object->set_defaults(supported); + + for (int i = 0; i < amount; i++) { + int name_tag = supported.get_key_at_index(i); + Vector3i range = supported.get_value_at_index(i); + + EditorPropertyInteger *prop = memnew(EditorPropertyInteger); + prop->setup(range.x, range.y, false, 1, false, false); + prop->set_object_and_property(object.ptr(), "keys/" + itos(name_tag)); + + String name = TS->tag_to_name(name_tag); + prop->set_label(name.capitalize()); + prop->set_tooltip_text(name); + prop->set_selectable(false); + + prop->connect("property_changed", callable_mp(this, &EditorPropertyOTVariation::_property_changed)); + prop->connect("object_id_selected", callable_mp(this, &EditorPropertyOTVariation::_object_id_selected)); + + property_vbox->add_child(prop); + + prop->update_property(); + } + + updating = false; + } else { + if (container) { + set_bottom_editor(nullptr); + memdelete(container); + container = nullptr; + } + } +} + +void EditorPropertyOTVariation::_edit_pressed() { + Variant prop_val = get_edited_object()->get(get_edited_property()); + if (prop_val.get_type() == Variant::NIL) { + Callable::CallError ce; + Variant::construct(Variant::DICTIONARY, prop_val, nullptr, 0, ce); + get_edited_object()->set(get_edited_property(), prop_val); + } + + get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed()); + update_property(); +} + +void EditorPropertyOTVariation::_page_changed(int p_page) { + if (updating) { + return; + } + page_index = p_page; + update_property(); +} + +EditorPropertyOTVariation::EditorPropertyOTVariation() { + object.instantiate(); + page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page")); + + edit = memnew(Button); + edit->set_h_size_flags(SIZE_EXPAND_FILL); + edit->set_clip_text(true); + edit->connect("pressed", callable_mp(this, &EditorPropertyOTVariation::_edit_pressed)); + edit->set_toggle_mode(true); + add_child(edit); + add_focusable(edit); +} + +/*************************************************************************/ +/* EditorPropertyOTFeatures */ +/*************************************************************************/ + +void EditorPropertyOTFeatures::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + if (button_add) { + button_add->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + } + } break; + } +} + +void EditorPropertyOTFeatures::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + if (p_property.begins_with("keys")) { + Dictionary dict = object->get_dict(); + int key = p_property.get_slice("/", 1).to_int(); + dict[key] = (int)p_value; + + emit_changed(get_edited_property(), dict, "", true); + + dict = dict.duplicate(); // Duplicate, so undo/redo works better. + object->set_dict(dict); + } +} + +void EditorPropertyOTFeatures::_remove(Object *p_button, int p_key) { + Dictionary dict = object->get_dict(); + + dict.erase(p_key); + + emit_changed(get_edited_property(), dict, "", false); + + dict = dict.duplicate(); // Duplicate, so undo/redo works better. + object->set_dict(dict); + update_property(); +} + +void EditorPropertyOTFeatures::_add_menu() { + Size2 size = get_size(); + menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); + menu->reset_size(); + menu->popup(); +} + +void EditorPropertyOTFeatures::_add_feature(int p_option) { + Dictionary dict = object->get_dict(); + + dict[p_option] = 1; + + emit_changed(get_edited_property(), dict, "", false); + + dict = dict.duplicate(); // Duplicate, so undo/redo works better. + object->set_dict(dict); + update_property(); +} + +void EditorPropertyOTFeatures::_object_id_selected(const StringName &p_property, ObjectID p_id) { + emit_signal(SNAME("object_id_selected"), p_property, p_id); +} + +void EditorPropertyOTFeatures::update_property() { + Variant updated_val = get_edited_object()->get(get_edited_property()); + + Dictionary dict = updated_val; + + Ref<Font> fd; + if (Object::cast_to<FontVariation>(get_edited_object()) != nullptr) { + fd = get_edited_object(); + } else if (Object::cast_to<DynamicFontImportSettingsData>(get_edited_object()) != nullptr) { + Ref<DynamicFontImportSettingsData> imp = Object::cast_to<DynamicFontImportSettingsData>(get_edited_object()); + fd = imp->get_font(); + } + + Dictionary supported; + if (fd.is_valid()) { + supported = fd->get_supported_feature_list(); + } + + if (supported.is_empty()) { + edit->set_text(vformat(TTR("No supported features"))); + if (container) { + set_bottom_editor(nullptr); + memdelete(container); + button_add = nullptr; + container = nullptr; + } + return; + } + edit->set_text(vformat(TTR("Features (%d of %d set)"), dict.size(), supported.size())); + + bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property()); + if (edit->is_pressed() != unfolded) { + edit->set_pressed(unfolded); + } + + if (unfolded) { + updating = true; + + if (!container) { + container = memnew(MarginContainer); + container->set_theme_type_variation("MarginContainer4px"); + add_child(container); + set_bottom_editor(container); + + VBoxContainer *vbox = memnew(VBoxContainer); + vbox->set_v_size_flags(SIZE_EXPAND_FILL); + container->add_child(vbox); + + property_vbox = memnew(VBoxContainer); + property_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + vbox->add_child(property_vbox); + + paginator = memnew(EditorPaginator); + paginator->connect("page_changed", callable_mp(this, &EditorPropertyOTFeatures::_page_changed)); + vbox->add_child(paginator); + } else { + // Queue children for deletion, deleting immediately might cause errors. + for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { + property_vbox->get_child(i)->queue_free(); + } + button_add = nullptr; + } + + // Update add menu items. + menu->clear(); + bool have_sub[FGRP_MAX]; + for (int i = 0; i < FGRP_MAX; i++) { + menu_sub[i]->clear(); + have_sub[i] = false; + } + + bool show_hidden = EDITOR_GET("interface/inspector/show_low_level_opentype_features"); + + for (int i = 0; i < supported.size(); i++) { + int name_tag = supported.get_key_at_index(i); + Dictionary info = supported.get_value_at_index(i); + bool hidden = info["hidden"].operator bool(); + String name = TS->tag_to_name(name_tag); + FeatureGroups grp = FGRP_MAX; + + if (hidden && !show_hidden) { + continue; + } + + if (name.begins_with("stylistic_set_")) { + grp = FGRP_STYLISTIC_SET; + } else if (name.begins_with("character_variant_")) { + grp = FGRP_CHARACTER_VARIANT; + } else if (name.ends_with("_capitals")) { + grp = FGRP_CAPITLS; + } else if (name.ends_with("_ligatures")) { + grp = FGRP_LIGATURES; + } else if (name.ends_with("_alternates")) { + grp = FGRP_ALTERNATES; + } else if (name.ends_with("_kanji_forms") || name.begins_with("jis") || name == "simplified_forms" || name == "traditional_name_forms" || name == "traditional_forms") { + grp = FGRP_EAL; + } else if (name.ends_with("_widths")) { + grp = FGRP_EAW; + } else if (name == "tabular_figures" || name == "proportional_figures") { + grp = FGRP_NUMAL; + } else if (name.begins_with("custom_")) { + grp = FGRP_CUSTOM; + } + String disp_name = name.capitalize(); + if (info.has("label")) { + disp_name = vformat("%s (%s)", disp_name, info["label"].operator String()); + } + + if (grp == FGRP_MAX) { + menu->add_item(disp_name, name_tag); + } else { + menu_sub[grp]->add_item(disp_name, name_tag); + have_sub[grp] = true; + } + } + for (int i = 0; i < FGRP_MAX; i++) { + if (have_sub[i]) { + menu->add_submenu_item(RTR(group_names[i]), "FTRMenu_" + itos(i)); + } + } + + int size = dict.size(); + + int max_page = MAX(0, size - 1) / page_length; + page_index = MIN(page_index, max_page); + + paginator->update(page_index, max_page); + paginator->set_visible(max_page > 0); + + int offset = page_index * page_length; + + int amount = MIN(size - offset, page_length); + + dict = dict.duplicate(); + object->set_dict(dict); + + for (int i = 0; i < amount; i++) { + int name_tag = dict.get_key_at_index(i); + + if (supported.has(name_tag)) { + Dictionary info = supported[name_tag]; + Variant::Type vtype = Variant::Type(info["type"].operator int()); + bool hidden = info["hidden"].operator bool(); + if (hidden && !show_hidden) { + continue; + } + + EditorProperty *prop = nullptr; + switch (vtype) { + case Variant::NIL: { + prop = memnew(EditorPropertyNil); + } break; + case Variant::BOOL: { + prop = memnew(EditorPropertyCheck); + } break; + case Variant::INT: { + EditorPropertyInteger *editor = memnew(EditorPropertyInteger); + editor->setup(0, 255, 1, false, false, false); + prop = editor; + } break; + default: { + ERR_CONTINUE_MSG(true, vformat("Unsupported OT feature data type %s", Variant::get_type_name(vtype))); + } + } + prop->set_object_and_property(object.ptr(), "keys/" + itos(name_tag)); + + String name = TS->tag_to_name(name_tag); + String disp_name = name.capitalize(); + if (info.has("label")) { + disp_name = vformat("%s (%s)", disp_name, info["label"].operator String()); + } + prop->set_label(disp_name); + prop->set_tooltip_text(name); + prop->set_selectable(false); + + prop->connect("property_changed", callable_mp(this, &EditorPropertyOTFeatures::_property_changed)); + prop->connect("object_id_selected", callable_mp(this, &EditorPropertyOTFeatures::_object_id_selected)); + + HBoxContainer *hbox = memnew(HBoxContainer); + property_vbox->add_child(hbox); + hbox->add_child(prop); + prop->set_h_size_flags(SIZE_EXPAND_FILL); + Button *remove = memnew(Button); + remove->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + hbox->add_child(remove); + remove->connect("pressed", callable_mp(this, &EditorPropertyOTFeatures::_remove).bind(remove, name_tag)); + + prop->update_property(); + } + } + + button_add = EditorInspector::create_inspector_action_button(TTR("Add Feature")); + button_add->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + button_add->connect("pressed", callable_mp(this, &EditorPropertyOTFeatures::_add_menu)); + property_vbox->add_child(button_add); + + updating = false; + } else { + if (container) { + set_bottom_editor(nullptr); + memdelete(container); + button_add = nullptr; + container = nullptr; + } + } +} + +void EditorPropertyOTFeatures::_edit_pressed() { + Variant prop_val = get_edited_object()->get(get_edited_property()); + if (prop_val.get_type() == Variant::NIL) { + Callable::CallError ce; + Variant::construct(Variant::DICTIONARY, prop_val, nullptr, 0, ce); + get_edited_object()->set(get_edited_property(), prop_val); + } + + get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed()); + update_property(); +} + +void EditorPropertyOTFeatures::_page_changed(int p_page) { + if (updating) { + return; + } + page_index = p_page; + update_property(); +} + +EditorPropertyOTFeatures::EditorPropertyOTFeatures() { + object.instantiate(); + page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page")); + + edit = memnew(Button); + edit->set_h_size_flags(SIZE_EXPAND_FILL); + edit->set_clip_text(true); + edit->connect("pressed", callable_mp(this, &EditorPropertyOTFeatures::_edit_pressed)); + edit->set_toggle_mode(true); + add_child(edit); + add_focusable(edit); + + menu = memnew(PopupMenu); + add_child(menu); + menu->connect("id_pressed", callable_mp(this, &EditorPropertyOTFeatures::_add_feature)); + + for (int i = 0; i < FGRP_MAX; i++) { + menu_sub[i] = memnew(PopupMenu); + menu_sub[i]->set_name("FTRMenu_" + itos(i)); + menu->add_child(menu_sub[i]); + menu_sub[i]->connect("id_pressed", callable_mp(this, &EditorPropertyOTFeatures::_add_feature)); + } + + group_names[FGRP_STYLISTIC_SET] = "Stylistic Sets"; + group_names[FGRP_CHARACTER_VARIANT] = "Character Variants"; + group_names[FGRP_CAPITLS] = "Capitals"; + group_names[FGRP_LIGATURES] = "Ligatures"; + group_names[FGRP_ALTERNATES] = "Alternates"; + group_names[FGRP_EAL] = "East Asian Language"; + group_names[FGRP_EAW] = "East Asian Widths"; + group_names[FGRP_NUMAL] = "Numeral Alignment"; + group_names[FGRP_CUSTOM] = "Custom"; +} + +/*************************************************************************/ +/* EditorInspectorPluginFontVariation */ +/*************************************************************************/ + +bool EditorInspectorPluginFontVariation::can_handle(Object *p_object) { + return (Object::cast_to<FontVariation>(p_object) != nullptr) || (Object::cast_to<DynamicFontImportSettingsData>(p_object) != nullptr); +} + +bool EditorInspectorPluginFontVariation::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) { + if (p_path == "variation_opentype") { + add_property_editor(p_path, memnew(EditorPropertyOTVariation)); + return true; + } else if (p_path == "opentype_features") { + add_property_editor(p_path, memnew(EditorPropertyOTFeatures)); + return true; + } else if (p_path == "language_support") { + add_property_editor(p_path, memnew(EditorPropertyFontMetaOverride(false))); + return true; + } else if (p_path == "script_support") { + add_property_editor(p_path, memnew(EditorPropertyFontMetaOverride(true))); + return true; + } + return false; +} + +/*************************************************************************/ +/* FontPreview */ +/*************************************************************************/ + +void FontPreview::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_DRAW: { + // Draw font name (style). + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); + Color text_color = get_theme_color(SNAME("font_color"), SNAME("Label")); + + // Draw font preview. + bool prev_ok = true; + if (prev_font.is_valid()) { + if (prev_font->get_font_name().is_empty()) { + prev_ok = false; + } else { + String name; + if (prev_font->get_font_style_name().is_empty()) { + name = prev_font->get_font_name(); + } else { + name = vformat("%s (%s)", prev_font->get_font_name(), prev_font->get_font_style_name()); + } + if (prev_font->is_class("FontVariation")) { + name += " " + TTR(" - Variation"); + } + font->draw_string(get_canvas_item(), Point2(0, font->get_height(font_size) + 2 * EDSCALE), name, HORIZONTAL_ALIGNMENT_CENTER, get_size().x, font_size, text_color); + + String sample; + static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀"; + for (int i = 0; i < sample_base.length(); i++) { + if (prev_font->has_char(sample_base[i])) { + sample += sample_base[i]; + } + } + if (sample.is_empty()) { + sample = prev_font->get_supported_chars().substr(0, 6); + } + if (sample.is_empty()) { + prev_ok = false; + } else { + prev_font->draw_string(get_canvas_item(), Point2(0, font->get_height(font_size) + prev_font->get_height(25 * EDSCALE)), sample, HORIZONTAL_ALIGNMENT_CENTER, get_size().x, 25 * EDSCALE, text_color); + } + } + } + if (!prev_ok) { + text_color.a *= 0.5; + font->draw_string(get_canvas_item(), Point2(0, font->get_height(font_size) + 2 * EDSCALE), TTR("Unable to preview font"), HORIZONTAL_ALIGNMENT_CENTER, get_size().x, font_size, text_color); + } + } break; + } +} + +void FontPreview::_bind_methods() {} + +Size2 FontPreview::get_minimum_size() const { + return Vector2(64, 64) * EDSCALE; +} + +void FontPreview::set_data(const Ref<Font> &p_f) { + prev_font = p_f; + queue_redraw(); +} + +FontPreview::FontPreview() { +} + +/*************************************************************************/ +/* EditorInspectorPluginFontPreview */ +/*************************************************************************/ + +bool EditorInspectorPluginFontPreview::can_handle(Object *p_object) { + return Object::cast_to<Font>(p_object) != nullptr; +} + +void EditorInspectorPluginFontPreview::parse_begin(Object *p_object) { + Font *fd = Object::cast_to<Font>(p_object); + ERR_FAIL_COND(!fd); + + FontPreview *editor = memnew(FontPreview); + editor->set_data(fd); + add_custom_control(editor); +} + +bool EditorInspectorPluginFontPreview::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) { + return false; +} + +/*************************************************************************/ +/* EditorPropertyFontNamesArray */ +/*************************************************************************/ + +void EditorPropertyFontNamesArray::_add_element() { + Size2 size = get_size(); + menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); + menu->reset_size(); + menu->popup(); +} + +void EditorPropertyFontNamesArray::_add_font(int p_option) { + if (updating) { + return; + } + + Variant array = object->get_array(); + int previous_size = array.call("size"); + + array.call("resize", previous_size + 1); + array.set(previous_size, menu->get_item_text(p_option)); + + emit_changed(get_edited_property(), array, "", false); + object->set_array(array); + update_property(); +} + +EditorPropertyFontNamesArray::EditorPropertyFontNamesArray() { + menu = memnew(PopupMenu); + menu->add_item("Sans-Serif", 0); + menu->add_item("Serif", 1); + menu->add_item("Monospace", 2); + menu->add_item("Fantasy", 3); + menu->add_item("Cursive", 4); + + menu->add_separator(); + + if (OS::get_singleton()) { + Vector<String> fonts = OS::get_singleton()->get_system_fonts(); + for (int i = 0; i < fonts.size(); i++) { + menu->add_item(fonts[i], i + 6); + } + } + add_child(menu); + menu->connect("id_pressed", callable_mp(this, &EditorPropertyFontNamesArray::_add_font)); +} + +/*************************************************************************/ +/* EditorInspectorPluginSystemFont */ +/*************************************************************************/ + +bool EditorInspectorPluginSystemFont::can_handle(Object *p_object) { + return Object::cast_to<SystemFont>(p_object) != nullptr; +} + +bool EditorInspectorPluginSystemFont::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) { + if (p_path == "font_names") { + EditorPropertyFontNamesArray *editor = memnew(EditorPropertyFontNamesArray); + editor->setup(p_type, p_hint_text); + add_property_editor(p_path, editor); + return true; + } + return false; +} + +/*************************************************************************/ +/* FontEditorPlugin */ +/*************************************************************************/ + +FontEditorPlugin::FontEditorPlugin() { + Ref<EditorInspectorPluginFontVariation> fc_plugin; + fc_plugin.instantiate(); + EditorInspector::add_inspector_plugin(fc_plugin); + + Ref<EditorInspectorPluginSystemFont> fs_plugin; + fs_plugin.instantiate(); + EditorInspector::add_inspector_plugin(fs_plugin); + + Ref<EditorInspectorPluginFontPreview> fp_plugin; + fp_plugin.instantiate(); + EditorInspector::add_inspector_plugin(fp_plugin); +} diff --git a/editor/plugins/font_config_plugin.h b/editor/plugins/font_config_plugin.h new file mode 100644 index 0000000000..6cea5967b2 --- /dev/null +++ b/editor/plugins/font_config_plugin.h @@ -0,0 +1,286 @@ +/**************************************************************************/ +/* font_config_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 FONT_CONFIG_PLUGIN_H +#define FONT_CONFIG_PLUGIN_H + +#include "core/io/marshalls.h" +#include "editor/editor_plugin.h" +#include "editor/editor_properties.h" +#include "editor/editor_properties_array_dict.h" + +/*************************************************************************/ + +class EditorPropertyFontMetaObject : public RefCounted { + GDCLASS(EditorPropertyFontMetaObject, RefCounted); + + Dictionary dict; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + static void _bind_methods(); + +public: + void set_dict(const Dictionary &p_dict); + Dictionary get_dict(); + + EditorPropertyFontMetaObject(){}; +}; + +/*************************************************************************/ + +class EditorPropertyFontOTObject : public RefCounted { + GDCLASS(EditorPropertyFontOTObject, RefCounted); + + Dictionary dict; + Dictionary defaults_dict; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + bool _property_can_revert(const StringName &p_name) const; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const; + +public: + void set_dict(const Dictionary &p_dict); + Dictionary get_dict(); + + void set_defaults(const Dictionary &p_dict); + Dictionary get_defaults(); + + EditorPropertyFontOTObject(){}; +}; + +/*************************************************************************/ + +class EditorPropertyFontMetaOverride : public EditorProperty { + GDCLASS(EditorPropertyFontMetaOverride, EditorProperty); + + Ref<EditorPropertyFontMetaObject> object; + + MarginContainer *container = nullptr; + VBoxContainer *property_vbox = nullptr; + + Button *button_add = nullptr; + Button *edit = nullptr; + PopupMenu *menu = nullptr; + EditorLocaleDialog *locale_select = nullptr; + + Vector<String> script_codes; + + bool script_editor = false; + bool updating = false; + int page_length = 20; + int page_index = 0; + EditorPaginator *paginator = nullptr; + +protected: + void _notification(int p_what); + static void _bind_methods(){}; + + void _edit_pressed(); + void _page_changed(int p_page); + void _property_changed(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false); + void _remove(Object *p_button, const String &p_key); + void _add_menu(); + void _add_script(int p_option); + void _add_lang(const String &p_locale); + void _object_id_selected(const StringName &p_property, ObjectID p_id); + +public: + virtual void update_property() override; + + EditorPropertyFontMetaOverride(bool p_script); +}; + +/*************************************************************************/ + +class EditorPropertyOTVariation : public EditorProperty { + GDCLASS(EditorPropertyOTVariation, EditorProperty); + + Ref<EditorPropertyFontOTObject> object; + + MarginContainer *container = nullptr; + VBoxContainer *property_vbox = nullptr; + + Button *edit = nullptr; + + bool updating = false; + int page_length = 20; + int page_index = 0; + EditorPaginator *paginator = nullptr; + +protected: + void _notification(int p_what); + static void _bind_methods(){}; + + void _edit_pressed(); + void _page_changed(int p_page); + void _property_changed(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false); + void _object_id_selected(const StringName &p_property, ObjectID p_id); + +public: + virtual void update_property() override; + + EditorPropertyOTVariation(); +}; + +/*************************************************************************/ + +class EditorPropertyOTFeatures : public EditorProperty { + GDCLASS(EditorPropertyOTFeatures, EditorProperty); + + enum FeatureGroups { + FGRP_STYLISTIC_SET, + FGRP_CHARACTER_VARIANT, + FGRP_CAPITLS, + FGRP_LIGATURES, + FGRP_ALTERNATES, + FGRP_EAL, + FGRP_EAW, + FGRP_NUMAL, + FGRP_CUSTOM, + FGRP_MAX, + }; + + Ref<EditorPropertyFontOTObject> object; + + MarginContainer *container = nullptr; + VBoxContainer *property_vbox = nullptr; + + Button *button_add = nullptr; + Button *edit = nullptr; + PopupMenu *menu = nullptr; + PopupMenu *menu_sub[FGRP_MAX]; + String group_names[FGRP_MAX]; + + bool updating = false; + int page_length = 20; + int page_index = 0; + EditorPaginator *paginator = nullptr; + +protected: + void _notification(int p_what); + static void _bind_methods(){}; + + void _edit_pressed(); + void _page_changed(int p_page); + void _property_changed(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false); + void _remove(Object *p_button, int p_key); + void _add_menu(); + void _add_feature(int p_option); + void _object_id_selected(const StringName &p_property, ObjectID p_id); + +public: + virtual void update_property() override; + + EditorPropertyOTFeatures(); +}; + +/*************************************************************************/ + +class EditorInspectorPluginFontVariation : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginFontVariation, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false) override; +}; + +/*************************************************************************/ + +class FontPreview : public Control { + GDCLASS(FontPreview, Control); + +protected: + void _notification(int p_what); + static void _bind_methods(); + + Ref<Font> prev_font; + +public: + virtual Size2 get_minimum_size() const override; + + void set_data(const Ref<Font> &p_f); + + FontPreview(); +}; + +/*************************************************************************/ + +class EditorInspectorPluginFontPreview : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginFontPreview, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false) override; +}; + +/*************************************************************************/ + +class EditorPropertyFontNamesArray : public EditorPropertyArray { + GDCLASS(EditorPropertyFontNamesArray, EditorPropertyArray); + + PopupMenu *menu = nullptr; + +protected: + virtual void _add_element() override; + + void _add_font(int p_option); + static void _bind_methods(){}; + +public: + EditorPropertyFontNamesArray(); +}; + +/*************************************************************************/ + +class EditorInspectorPluginSystemFont : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginSystemFont, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false) override; +}; + +/*************************************************************************/ + +class FontEditorPlugin : public EditorPlugin { + GDCLASS(FontEditorPlugin, EditorPlugin); + +public: + FontEditorPlugin(); + + virtual String get_name() const override { return "Font"; } +}; + +#endif // FONT_CONFIG_PLUGIN_H diff --git a/editor/plugins/font_editor_plugin.cpp b/editor/plugins/font_editor_plugin.cpp deleted file mode 100644 index 73a6781774..0000000000 --- a/editor/plugins/font_editor_plugin.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/*************************************************************************/ -/* font_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "font_editor_plugin.h" - -#include "editor/editor_scale.h" - -void FontDataPreview::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - Color text_color = get_theme_color(SNAME("font_color"), SNAME("Label")); - Color line_color = text_color; - line_color.a *= 0.6; - Vector2 pos = (get_size() - line->get_size()) / 2; - line->draw(get_canvas_item(), pos, text_color); - draw_line(Vector2(0, pos.y + line->get_line_ascent()), Vector2(pos.x - 5, pos.y + line->get_line_ascent()), line_color); - draw_line(Vector2(pos.x + line->get_size().x + 5, pos.y + line->get_line_ascent()), Vector2(get_size().x, pos.y + line->get_line_ascent()), line_color); - } -} - -void FontDataPreview::_bind_methods() {} - -Size2 FontDataPreview::get_minimum_size() const { - return Vector2(64, 64) * EDSCALE; -} - -void FontDataPreview::set_data(const Ref<FontData> &p_data) { - Ref<Font> f = memnew(Font); - f->add_data(p_data); - - line->clear(); - if (p_data.is_valid()) { - String sample; - static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀"; - for (int i = 0; i < sample_base.length(); i++) { - if (p_data->has_char(sample_base[i])) { - sample += sample_base[i]; - } - } - if (sample.is_empty()) { - sample = p_data->get_supported_chars().substr(0, 6); - } - line->add_string(sample, f, 72); - } - - update(); -} - -FontDataPreview::FontDataPreview() { - line.instantiate(); -} - -/*************************************************************************/ - -bool EditorInspectorPluginFont::can_handle(Object *p_object) { - return Object::cast_to<FontData>(p_object) != nullptr; -} - -void EditorInspectorPluginFont::parse_begin(Object *p_object) { - FontData *fd = Object::cast_to<FontData>(p_object); - ERR_FAIL_COND(!fd); - - FontDataPreview *editor = memnew(FontDataPreview); - editor->set_data(fd); - add_custom_control(editor); -} - -bool EditorInspectorPluginFont::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { - return false; -} - -/*************************************************************************/ - -FontEditorPlugin::FontEditorPlugin(EditorNode *p_node) { - Ref<EditorInspectorPluginFont> fd_plugin; - fd_plugin.instantiate(); - EditorInspector::add_inspector_plugin(fd_plugin); -} diff --git a/editor/plugins/font_editor_plugin.h b/editor/plugins/font_editor_plugin.h deleted file mode 100644 index 736137121a..0000000000 --- a/editor/plugins/font_editor_plugin.h +++ /dev/null @@ -1,78 +0,0 @@ -/*************************************************************************/ -/* font_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 FONT_EDITOR_PLUGIN_H -#define FONT_EDITOR_PLUGIN_H - -#include "editor/editor_node.h" -#include "editor/editor_plugin.h" -#include "scene/resources/font.h" -#include "scene/resources/text_line.h" - -class FontDataPreview : public Control { - GDCLASS(FontDataPreview, Control); - -protected: - void _notification(int p_what); - static void _bind_methods(); - - Ref<TextLine> line; - -public: - virtual Size2 get_minimum_size() const override; - - void set_data(const Ref<FontData> &p_data); - - FontDataPreview(); -}; - -/*************************************************************************/ - -class EditorInspectorPluginFont : public EditorInspectorPlugin { - GDCLASS(EditorInspectorPluginFont, EditorInspectorPlugin); - -public: - virtual bool can_handle(Object *p_object) override; - virtual void parse_begin(Object *p_object) override; - virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; -}; - -/*************************************************************************/ - -class FontEditorPlugin : public EditorPlugin { - GDCLASS(FontEditorPlugin, EditorPlugin); - -public: - FontEditorPlugin(EditorNode *p_node); - - virtual String get_name() const override { return "Font"; } -}; - -#endif // FONT_EDITOR_PLUGIN_H diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h new file mode 100644 index 0000000000..f3d2867030 --- /dev/null +++ b/editor/plugins/gdextension_export_plugin.h @@ -0,0 +1,122 @@ +/**************************************************************************/ +/* gdextension_export_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 GDEXTENSION_EXPORT_PLUGIN_H +#define GDEXTENSION_EXPORT_PLUGIN_H + +#include "editor/export/editor_export.h" + +class GDExtensionExportPlugin : public EditorExportPlugin { +protected: + virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features); + virtual String _get_name() const { return "GDExtension"; } +}; + +void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { + if (p_type != "GDExtension") { + return; + } + + Ref<ConfigFile> config; + config.instantiate(); + + Error err = config->load(p_path); + ERR_FAIL_COND_MSG(err, "Failed to load GDExtension file: " + p_path); + + ERR_FAIL_COND_MSG(!config->has_section_key("configuration", "entry_symbol"), "Failed to export GDExtension file, missing entry symbol: " + p_path); + + String entry_symbol = config->get_value("configuration", "entry_symbol"); + + PackedStringArray tags; + String library_path = GDExtension::find_extension_library( + p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); }, &tags); + if (!library_path.is_empty()) { + add_shared_object(library_path, tags); + + if (p_features.has("iOS") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { + String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" + "extern void add_ios_init_callback(void (*cb)());\n" + "\n" + "extern \"C\" void $ENTRY();\n" + "void $ENTRY_init() {\n" + " if (&$ENTRY) register_dynamic_symbol((char *)\"$ENTRY\", (void *)$ENTRY);\n" + "}\n" + "struct $ENTRY_struct {\n" + " $ENTRY_struct() {\n" + " add_ios_init_callback($ENTRY_init);\n" + " }\n" + "};\n" + "$ENTRY_struct $ENTRY_struct_instance;\n\n"; + additional_code = additional_code.replace("$ENTRY", entry_symbol); + add_ios_cpp_code(additional_code); + + String linker_flags = "-Wl,-U,_" + entry_symbol; + add_ios_linker_flags(linker_flags); + } + } else { + Vector<String> features_vector; + for (const String &E : p_features) { + features_vector.append(E); + } + ERR_FAIL_MSG(vformat("No suitable library found for GDExtension: %s. Possible feature flags for your platform: %s", p_path, String(", ").join(features_vector))); + } + + List<String> dependencies; + if (config->has_section("dependencies")) { + config->get_section_keys("dependencies", &dependencies); + } + + for (const String &E : dependencies) { + Vector<String> dependency_tags = E.split("."); + bool all_tags_met = true; + for (int i = 0; i < dependency_tags.size(); i++) { + String tag = dependency_tags[i].strip_edges(); + if (!p_features.has(tag)) { + all_tags_met = false; + break; + } + } + + if (all_tags_met) { + Dictionary dependency = config->get_value("dependencies", E); + for (const Variant *key = dependency.next(nullptr); key; key = dependency.next(key)) { + String dependency_path = *key; + String target_path = dependency[*key]; + if (dependency_path.is_relative_path()) { + dependency_path = p_path.get_base_dir().path_join(dependency_path); + } + add_shared_object(dependency_path, dependency_tags, target_path); + } + break; + } + } +} + +#endif // GDEXTENSION_EXPORT_PLUGIN_H diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp index 6b93a1872d..04b2a9337e 100644 --- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_2d_editor_plugin.cpp @@ -1,40 +1,45 @@ -/*************************************************************************/ -/* gpu_particles_2d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* gpu_particles_2d_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 "gpu_particles_2d_editor_plugin.h" #include "canvas_item_editor_plugin.h" #include "core/io/image_loader.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/scene_tree_dock.h" #include "scene/2d/cpu_particles_2d.h" +#include "scene/gui/menu_button.h" #include "scene/gui/separator.h" -#include "scene/resources/particles_material.h" +#include "scene/resources/particle_process_material.h" void GPUParticles2DEditorPlugin::edit(Object *p_object) { particles = Object::cast_to<GPUParticles2D>(p_object); @@ -58,7 +63,7 @@ void GPUParticles2DEditorPlugin::_file_selected(const String &p_file) { } void GPUParticles2DEditorPlugin::_selection_changed() { - List<Node *> selected_nodes = editor->get_editor_selection()->get_selected_node_list(); + List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list(); if (selected_particles.is_empty() && selected_nodes.is_empty()) { return; @@ -108,11 +113,11 @@ void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) { cpu_particles->set_process_mode(particles->get_process_mode()); cpu_particles->set_z_index(particles->get_z_index()); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Convert to CPUParticles2D")); - ur->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", particles, cpu_particles, true, false); + ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", particles, cpu_particles, true, false); ur->add_do_reference(cpu_particles); - ur->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", cpu_particles, particles, false, false); + ur->add_undo_method(SceneTreeDock::get_singleton(), "replace_node", cpu_particles, particles, false, false); ur->add_undo_reference(particles); ur->commit_action(); @@ -156,6 +161,7 @@ void GPUParticles2DEditorPlugin::_generate_visibility_rect() { particles->set_emitting(false); } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Generate Visibility Rect")); undo_redo->add_do_method(particles, "set_visibility_rect", rect); undo_redo->add_undo_method(particles, "set_visibility_rect", particles->get_visibility_rect()); @@ -163,9 +169,9 @@ void GPUParticles2DEditorPlugin::_generate_visibility_rect() { } void GPUParticles2DEditorPlugin::_generate_emission_mask() { - Ref<ParticlesMaterial> pm = particles->get_process_material(); + Ref<ParticleProcessMaterial> pm = particles->get_process_material(); if (!pm.is_valid()) { - EditorNode::get_singleton()->show_warning(TTR("Can only set point into a ParticlesMaterial process material")); + EditorNode::get_singleton()->show_warning(TTR("Can only set point into a ParticleProcessMaterial process material")); return; } @@ -203,8 +209,8 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { int vpc = 0; { - Vector<uint8_t> data = img->get_data(); - const uint8_t *r = data.ptr(); + Vector<uint8_t> img_data = img->get_data(); + const uint8_t *r = img_data.ptr(); for (int i = 0; i < s.width; i++) { for (int j = 0; j < s.height; j++) { @@ -287,7 +293,7 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { { uint8_t *tw = texdata.ptrw(); - float *twf = (float *)tw; + float *twf = reinterpret_cast<float *>(tw); for (int i = 0; i < vpc; i++) { twf[i * 2 + 0] = valid_positions[i].x; twf[i * 2 + 1] = valid_positions[i].y; @@ -295,13 +301,8 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { } img.instantiate(); - img->create(w, h, false, Image::FORMAT_RGF, texdata); - - Ref<ImageTexture> imgt; - imgt.instantiate(); - imgt->create_from_image(img); - - pm->set_emission_point_texture(imgt); + img->set_data(w, h, false, Image::FORMAT_RGF, texdata); + pm->set_emission_point_texture(ImageTexture::create_from_image(img)); pm->set_emission_point_count(vpc); if (capture_colors) { @@ -316,22 +317,19 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { } img.instantiate(); - img->create(w, h, false, Image::FORMAT_RGBA8, colordata); - - imgt.instantiate(); - imgt->create_from_image(img); - pm->set_emission_color_texture(imgt); + img->set_data(w, h, false, Image::FORMAT_RGBA8, colordata); + pm->set_emission_color_texture(ImageTexture::create_from_image(img)); } if (valid_normals.size()) { - pm->set_emission_shape(ParticlesMaterial::EMISSION_SHAPE_DIRECTED_POINTS); + pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); Vector<uint8_t> normdata; normdata.resize(w * h * 2 * sizeof(float)); //use RG texture { uint8_t *tw = normdata.ptrw(); - float *twf = (float *)tw; + float *twf = reinterpret_cast<float *>(tw); for (int i = 0; i < vpc; i++) { twf[i * 2 + 0] = valid_normals[i].x; twf[i * 2 + 1] = valid_normals[i].y; @@ -339,33 +337,30 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { } img.instantiate(); - img->create(w, h, false, Image::FORMAT_RGF, normdata); - - imgt.instantiate(); - imgt->create_from_image(img); - pm->set_emission_normal_texture(imgt); + img->set_data(w, h, false, Image::FORMAT_RGF, normdata); + pm->set_emission_normal_texture(ImageTexture::create_from_image(img)); } else { - pm->set_emission_shape(ParticlesMaterial::EMISSION_SHAPE_POINTS); + pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS); } } void GPUParticles2DEditorPlugin::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - menu->get_popup()->connect("id_pressed", callable_mp(this, &GPUParticles2DEditorPlugin::_menu_callback)); - menu->set_icon(menu->get_theme_icon(SNAME("GPUParticles2D"), SNAME("EditorIcons"))); - file->connect("file_selected", callable_mp(this, &GPUParticles2DEditorPlugin::_file_selected)); - EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &GPUParticles2DEditorPlugin::_selection_changed)); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + menu->get_popup()->connect("id_pressed", callable_mp(this, &GPUParticles2DEditorPlugin::_menu_callback)); + menu->set_icon(menu->get_theme_icon(SNAME("GPUParticles2D"), SNAME("EditorIcons"))); + file->connect("file_selected", callable_mp(this, &GPUParticles2DEditorPlugin::_file_selected)); + EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &GPUParticles2DEditorPlugin::_selection_changed)); + } break; } } void GPUParticles2DEditorPlugin::_bind_methods() { } -GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin(EditorNode *p_node) { +GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() { particles = nullptr; - editor = p_node; - undo_redo = editor->get_undo_redo(); toolbar = memnew(HBoxContainer); add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar); @@ -387,7 +382,7 @@ GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin(EditorNode *p_node) { List<String> ext; ImageLoader::get_recognized_extensions(&ext); for (const String &E : ext) { - file->add_filter("*." + E + "; " + E.to_upper()); + file->add_filter("*." + E, E.to_upper()); } file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); toolbar->add_child(file); diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.h b/editor/plugins/gpu_particles_2d_editor_plugin.h index 55e455e252..aa6d166d85 100644 --- a/editor/plugins/gpu_particles_2d_editor_plugin.h +++ b/editor/plugins/gpu_particles_2d_editor_plugin.h @@ -1,42 +1,47 @@ -/*************************************************************************/ -/* gpu_particles_2d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 PARTICLES_2D_EDITOR_PLUGIN_H -#define PARTICLES_2D_EDITOR_PLUGIN_H - -#include "editor/editor_node.h" +/**************************************************************************/ +/* gpu_particles_2d_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 GPU_PARTICLES_2D_EDITOR_PLUGIN_H +#define GPU_PARTICLES_2D_EDITOR_PLUGIN_H + #include "editor/editor_plugin.h" #include "scene/2d/collision_polygon_2d.h" #include "scene/2d/gpu_particles_2d.h" #include "scene/gui/box_container.h" -#include "scene/gui/file_dialog.h" +#include "scene/gui/spin_box.h" + +class CheckBox; +class ConfirmationDialog; +class EditorFileDialog; +class MenuButton; +class OptionButton; class GPUParticles2DEditorPlugin : public EditorPlugin { GDCLASS(GPUParticles2DEditorPlugin, EditorPlugin); @@ -55,27 +60,25 @@ class GPUParticles2DEditorPlugin : public EditorPlugin { EMISSION_MODE_BORDER_DIRECTED }; - GPUParticles2D *particles; + GPUParticles2D *particles = nullptr; List<GPUParticles2D *> selected_particles; - EditorFileDialog *file; - EditorNode *editor; + EditorFileDialog *file = nullptr; - HBoxContainer *toolbar; - MenuButton *menu; + HBoxContainer *toolbar = nullptr; + MenuButton *menu = nullptr; - SpinBox *epoints; + SpinBox *epoints = nullptr; - ConfirmationDialog *generate_visibility_rect; - SpinBox *generate_seconds; + ConfirmationDialog *generate_visibility_rect = nullptr; + SpinBox *generate_seconds = nullptr; - ConfirmationDialog *emission_mask; - OptionButton *emission_mask_mode; - CheckBox *emission_colors; + ConfirmationDialog *emission_mask = nullptr; + OptionButton *emission_mask_mode = nullptr; + CheckBox *emission_colors = nullptr; String source_emission_file; - UndoRedo *undo_redo; void _file_selected(const String &p_file); void _menu_callback(int p_idx); void _generate_visibility_rect(); @@ -93,8 +96,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - GPUParticles2DEditorPlugin(EditorNode *p_node); + GPUParticles2DEditorPlugin(); ~GPUParticles2DEditorPlugin(); }; -#endif // PARTICLES_2D_EDITOR_PLUGIN_H +#endif // GPU_PARTICLES_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp index 0057566603..65f66c2661 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp @@ -1,46 +1,51 @@ -/*************************************************************************/ -/* gpu_particles_3d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* gpu_particles_3d_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 "gpu_particles_3d_editor_plugin.h" #include "core/io/resource_loader.h" +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/plugins/node_3d_editor_plugin.h" +#include "editor/scene_tree_dock.h" #include "scene/3d/cpu_particles_3d.h" -#include "scene/resources/particles_material.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/gui/menu_button.h" +#include "scene/resources/particle_process_material.h" bool GPUParticles3DEditorBase::_generate(Vector<Vector3> &points, Vector<Vector3> &normals) { bool use_normals = emission_fill->get_selected() == 1; if (emission_fill->get_selected() < 2) { float area_accum = 0; - Map<float, int> triangle_area_map; + RBMap<float, int> triangle_area_map; for (int i = 0; i < geometry.size(); i++) { float area = geometry[i].get_area(); @@ -61,9 +66,9 @@ bool GPUParticles3DEditorBase::_generate(Vector<Vector3> &points, Vector<Vector3 for (int i = 0; i < emissor_count; i++) { float areapos = Math::random(0.0f, area_accum); - Map<float, int>::Element *E = triangle_area_map.find_closest(areapos); + RBMap<float, int>::Iterator E = triangle_area_map.find_closest(areapos); ERR_FAIL_COND_V(!E, false); - int index = E->get(); + int index = E->value; ERR_FAIL_INDEX_V(index, geometry.size(), false); // ok FINALLY get face @@ -164,20 +169,20 @@ void GPUParticles3DEditorBase::_node_selected(const NodePath &p_path) { return; } - VisualInstance3D *vi = Object::cast_to<VisualInstance3D>(sel); - if (!vi) { + MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(sel); + if (!mi || mi->get_mesh().is_null()) { EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain geometry."), sel->get_name())); return; } - geometry = vi->get_faces(VisualInstance3D::FACES_SOLID); + geometry = mi->get_mesh()->get_faces(); if (geometry.size() == 0) { EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain face geometry."), sel->get_name())); return; } - Transform3D geom_xform = base_node->get_global_transform().affine_inverse() * vi->get_global_transform(); + Transform3D geom_xform = base_node->get_global_transform().affine_inverse() * mi->get_global_transform(); int gc = geometry.size(); Face3 *w = geometry.ptrw(); @@ -211,9 +216,9 @@ GPUParticles3DEditorBase::GPUParticles3DEditorBase() { emission_fill->add_item(TTR("Surface Points")); emission_fill->add_item(TTR("Surface Points+Normal (Directed)")); emission_fill->add_item(TTR("Volume")); - emd_vb->add_margin_child(TTR("Emission Source: "), emission_fill); + emd_vb->add_margin_child(TTR("Emission Source:"), emission_fill); - emission_dialog->get_ok_button()->set_text(TTR("Create")); + emission_dialog->set_ok_button_text(TTR("Create")); emission_dialog->connect("confirmed", callable_mp(this, &GPUParticles3DEditorBase::_generate_emission_points)); emission_tree_dialog = memnew(SceneTreeDialog); @@ -229,9 +234,11 @@ void GPUParticles3DEditor::_node_removed(Node *p_node) { } void GPUParticles3DEditor::_notification(int p_notification) { - if (p_notification == NOTIFICATION_ENTER_TREE) { - options->set_icon(options->get_popup()->get_theme_icon(SNAME("GPUParticles3D"), SNAME("EditorIcons"))); - get_tree()->connect("node_removed", callable_mp(this, &GPUParticles3DEditor::_node_removed)); + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: { + options->set_icon(options->get_popup()->get_theme_icon(SNAME("GPUParticles3D"), SNAME("EditorIcons"))); + get_tree()->connect("node_removed", callable_mp(this, &GPUParticles3DEditor::_node_removed)); + } break; } } @@ -250,9 +257,9 @@ void GPUParticles3DEditor::_menu_option(int p_option) { } } break; case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: { - Ref<ParticlesMaterial> material = node->get_process_material(); - if (material.is_null()) { - EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticlesMaterial' is required.")); + Ref<ParticleProcessMaterial> mat = node->get_process_material(); + if (mat.is_null()) { + EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticleProcessMaterial' is required.")); return; } @@ -267,11 +274,11 @@ void GPUParticles3DEditor::_menu_option(int p_option) { cpu_particles->set_visible(node->is_visible()); cpu_particles->set_process_mode(node->get_process_mode()); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Convert to CPUParticles3D")); - ur->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", node, cpu_particles, true, false); + ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", node, cpu_particles, true, false); ur->add_do_reference(cpu_particles); - ur->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", cpu_particles, node, false, false); + ur->add_undo_method(SceneTreeDock::get_singleton(), "replace_node", cpu_particles, node, false, false); ur->add_undo_reference(node); ur->commit_action(); @@ -317,7 +324,7 @@ void GPUParticles3DEditor::_generate_aabb() { node->set_emitting(false); } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Generate Visibility AABB")); ur->add_do_method(node, "set_visibility_aabb", rect); ur->add_undo_method(node, "set_visibility_aabb", node->get_visibility_aabb()); @@ -350,7 +357,7 @@ void GPUParticles3DEditor::_generate_emission_points() { uint8_t *iw = point_img.ptrw(); memset(iw, 0, w * h * 3 * sizeof(float)); const Vector3 *r = points.ptr(); - float *wf = (float *)iw; + float *wf = reinterpret_cast<float *>(iw); for (int i = 0; i < point_count; i++) { wf[i * 3 + 0] = r[i].x; wf[i * 3 + 1] = r[i].y; @@ -359,18 +366,15 @@ void GPUParticles3DEditor::_generate_emission_points() { } Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img)); + Ref<ImageTexture> tex = ImageTexture::create_from_image(image); - Ref<ImageTexture> tex; - tex.instantiate(); - tex->create_from_image(image); - - Ref<ParticlesMaterial> material = node->get_process_material(); - ERR_FAIL_COND(material.is_null()); + Ref<ParticleProcessMaterial> mat = node->get_process_material(); + ERR_FAIL_COND(mat.is_null()); if (normals.size() > 0) { - material->set_emission_shape(ParticlesMaterial::EMISSION_SHAPE_DIRECTED_POINTS); - material->set_emission_point_count(point_count); - material->set_emission_point_texture(tex); + mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); + mat->set_emission_point_count(point_count); + mat->set_emission_point_texture(tex); Vector<uint8_t> point_img2; point_img2.resize(w * h * 3 * sizeof(float)); @@ -379,7 +383,7 @@ void GPUParticles3DEditor::_generate_emission_points() { uint8_t *iw = point_img2.ptrw(); memset(iw, 0, w * h * 3 * sizeof(float)); const Vector3 *r = normals.ptr(); - float *wf = (float *)iw; + float *wf = reinterpret_cast<float *>(iw); for (int i = 0; i < point_count; i++) { wf[i * 3 + 0] = r[i].x; wf[i * 3 + 1] = r[i].y; @@ -388,16 +392,11 @@ void GPUParticles3DEditor::_generate_emission_points() { } Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2)); - - Ref<ImageTexture> tex2; - tex2.instantiate(); - tex2->create_from_image(image2); - - material->set_emission_normal_texture(tex2); + mat->set_emission_normal_texture(ImageTexture::create_from_image(image2)); } else { - material->set_emission_shape(ParticlesMaterial::EMISSION_SHAPE_POINTS); - material->set_emission_point_count(point_count); - material->set_emission_point_texture(tex); + mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS); + mat->set_emission_point_count(point_count); + mat->set_emission_point_texture(tex); } } @@ -455,10 +454,9 @@ void GPUParticles3DEditorPlugin::make_visible(bool p_visible) { } } -GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() { particles_editor = memnew(GPUParticles3DEditor); - editor->get_main_control()->add_child(particles_editor); + EditorNode::get_singleton()->get_main_screen_control()->add_child(particles_editor); particles_editor->hide(); } diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.h b/editor/plugins/gpu_particles_3d_editor_plugin.h index f7e4244ba4..176a45df56 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.h +++ b/editor/plugins/gpu_particles_3d_editor_plugin.h @@ -1,60 +1,65 @@ -/*************************************************************************/ -/* gpu_particles_3d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 PARTICLES_EDITOR_PLUGIN_H -#define PARTICLES_EDITOR_PLUGIN_H - -#include "editor/editor_node.h" +/**************************************************************************/ +/* gpu_particles_3d_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 GPU_PARTICLES_3D_EDITOR_PLUGIN_H +#define GPU_PARTICLES_3D_EDITOR_PLUGIN_H + #include "editor/editor_plugin.h" #include "scene/3d/gpu_particles_3d.h" #include "scene/gui/spin_box.h" +class ConfirmationDialog; +class HBoxContainer; +class MenuButton; +class OptionButton; +class SceneTreeDialog; + class GPUParticles3DEditorBase : public Control { GDCLASS(GPUParticles3DEditorBase, Control); protected: - Node3D *base_node; - Panel *panel; - MenuButton *options; - HBoxContainer *particles_editor_hb; + Node3D *base_node = nullptr; + Panel *panel = nullptr; + MenuButton *options = nullptr; + HBoxContainer *particles_editor_hb = nullptr; - SceneTreeDialog *emission_tree_dialog; + SceneTreeDialog *emission_tree_dialog = nullptr; - ConfirmationDialog *emission_dialog; - SpinBox *emission_amount; - OptionButton *emission_fill; + ConfirmationDialog *emission_dialog = nullptr; + SpinBox *emission_amount = nullptr; + OptionButton *emission_fill = nullptr; Vector<Face3> geometry; bool _generate(Vector<Vector3> &points, Vector<Vector3> &normals); - virtual void _generate_emission_points() = 0; + virtual void _generate_emission_points(){}; void _node_selected(const NodePath &p_path); static void _bind_methods(); @@ -66,9 +71,9 @@ public: class GPUParticles3DEditor : public GPUParticles3DEditorBase { GDCLASS(GPUParticles3DEditor, GPUParticles3DEditorBase); - ConfirmationDialog *generate_aabb; - SpinBox *generate_seconds; - GPUParticles3D *node; + ConfirmationDialog *generate_aabb = nullptr; + SpinBox *generate_seconds = nullptr; + GPUParticles3D *node = nullptr; enum Menu { MENU_OPTION_GENERATE_AABB, @@ -100,8 +105,7 @@ public: class GPUParticles3DEditorPlugin : public EditorPlugin { GDCLASS(GPUParticles3DEditorPlugin, EditorPlugin); - GPUParticles3DEditor *particles_editor; - EditorNode *editor; + GPUParticles3DEditor *particles_editor = nullptr; public: virtual String get_name() const override { return "GPUParticles3D"; } @@ -110,8 +114,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - GPUParticles3DEditorPlugin(EditorNode *p_node); + GPUParticles3DEditorPlugin(); ~GPUParticles3DEditorPlugin(); }; -#endif // PARTICLES_EDITOR_PLUGIN_H +#endif // GPU_PARTICLES_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp index 1b4c944876..477a094d01 100644 --- a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp @@ -1,35 +1,38 @@ -/*************************************************************************/ -/* gpu_particles_collision_sdf_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* gpu_particles_collision_sdf_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 "gpu_particles_collision_sdf_editor_plugin.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" + void GPUParticlesCollisionSDF3DEditorPlugin::_bake() { if (col_sdf) { if (col_sdf->get_texture().is_null() || !col_sdf->get_texture()->get_path().is_resource_file()) { @@ -63,41 +66,43 @@ bool GPUParticlesCollisionSDF3DEditorPlugin::handles(Object *p_object) const { } void GPUParticlesCollisionSDF3DEditorPlugin::_notification(int p_what) { - if (p_what == NOTIFICATION_PROCESS) { - if (!col_sdf) { - return; - } + switch (p_what) { + case NOTIFICATION_PROCESS: { + if (!col_sdf) { + return; + } - // Set information tooltip on the Bake button. This information is useful - // to optimize performance (video RAM size) and reduce collision tunneling (individual cell size). + // Set information tooltip on the Bake button. This information is useful + // to optimize performance (video RAM size) and reduce collision tunneling (individual cell size). - const Vector3i size = col_sdf->get_estimated_cell_size(); + const Vector3i size = col_sdf->get_estimated_cell_size(); - const Vector3 extents = col_sdf->get_extents(); + const Vector3 extents = col_sdf->get_size() / 2; - int data_size = 2; - const double size_mb = size.x * size.y * size.z * data_size / (1024.0 * 1024.0); - // Add a qualitative measurement to help the user assess whether a GPUParticlesCollisionSDF3D node is using a lot of VRAM. - String size_quality; - if (size_mb < 8.0) { - size_quality = TTR("Low"); - } else if (size_mb < 32.0) { - size_quality = TTR("Moderate"); - } else { - size_quality = TTR("High"); - } + int data_size = 2; + const double size_mb = size.x * size.y * size.z * data_size / (1024.0 * 1024.0); + // Add a qualitative measurement to help the user assess whether a GPUParticlesCollisionSDF3D node is using a lot of VRAM. + String size_quality; + if (size_mb < 8.0) { + size_quality = TTR("Low"); + } else if (size_mb < 32.0) { + size_quality = TTR("Moderate"); + } else { + size_quality = TTR("High"); + } - String text; - text += vformat(TTR("Subdivisions: %s"), vformat(String::utf8("%d × %d × %d"), size.x, size.y, size.z)) + "\n"; - text += vformat(TTR("Cell size: %s"), vformat(String::utf8("%.3f × %.3f × %.3f"), extents.x / size.x, extents.y / size.y, extents.z / size.z)) + "\n"; - text += vformat(TTR("Video RAM size: %s MB (%s)"), String::num(size_mb, 2), size_quality); + String text; + text += vformat(TTR("Subdivisions: %s"), vformat(String::utf8("%d × %d × %d"), size.x, size.y, size.z)) + "\n"; + text += vformat(TTR("Cell size: %s"), vformat(String::utf8("%.3f × %.3f × %.3f"), extents.x / size.x, extents.y / size.y, extents.z / size.z)) + "\n"; + text += vformat(TTR("Video RAM size: %s MB (%s)"), String::num(size_mb, 2), size_quality); - // Only update the tooltip when needed to avoid constant redrawing. - if (bake->get_tooltip(Point2()) == text) { - return; - } + // Only update the tooltip when needed to avoid constant redrawing. + if (bake->get_tooltip(Point2()) == text) { + return; + } - bake->set_tooltip(text); + bake->set_tooltip_text(text); + } break; } } @@ -135,7 +140,7 @@ void GPUParticlesCollisionSDF3DEditorPlugin::_sdf_save_path_and_bake(const Strin if (col_sdf) { Ref<Image> bake_img = col_sdf->bake(); if (bake_img.is_null()) { - EditorNode::get_singleton()->show_warning(TTR("Bake Error.")); + EditorNode::get_singleton()->show_warning(TTR("No faces detected during GPUParticlesCollisionSDF3D bake.\nCheck whether there are visible meshes matching the bake mask within its extents.")); return; } @@ -147,7 +152,7 @@ void GPUParticlesCollisionSDF3DEditorPlugin::_sdf_save_path_and_bake(const Strin } config->set_value("remap", "importer", "3d_texture"); - config->set_value("remap", "type", "StreamTexture3D"); + config->set_value("remap", "type", "CompressedTexture3D"); if (!config->has_section_key("params", "compress/mode")) { config->set_value("params", "compress/mode", 3); //user may want another compression, so leave it be } @@ -171,14 +176,13 @@ void GPUParticlesCollisionSDF3DEditorPlugin::_sdf_save_path_and_bake(const Strin void GPUParticlesCollisionSDF3DEditorPlugin::_bind_methods() { } -GPUParticlesCollisionSDF3DEditorPlugin::GPUParticlesCollisionSDF3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +GPUParticlesCollisionSDF3DEditorPlugin::GPUParticlesCollisionSDF3DEditorPlugin() { bake_hb = memnew(HBoxContainer); bake_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); bake_hb->hide(); bake = memnew(Button); bake->set_flat(true); - bake->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); + bake->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); bake->set_text(TTR("Bake SDF")); bake->connect("pressed", callable_mp(this, &GPUParticlesCollisionSDF3DEditorPlugin::_bake)); bake_hb->add_child(bake); diff --git a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h index d74986f22b..9f59f6e2cd 100644 --- a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h @@ -1,51 +1,53 @@ -/*************************************************************************/ -/* gpu_particles_collision_sdf_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* gpu_particles_collision_sdf_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 GPU_PARTICLES_COLLISION_SDF_EDITOR_PLUGIN_H #define GPU_PARTICLES_COLLISION_SDF_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/gpu_particles_collision_3d.h" #include "scene/resources/material.h" +struct EditorProgress; +class EditorFileDialog; +class HBoxContainer; + class GPUParticlesCollisionSDF3DEditorPlugin : public EditorPlugin { GDCLASS(GPUParticlesCollisionSDF3DEditorPlugin, EditorPlugin); - GPUParticlesCollisionSDF3D *col_sdf; + GPUParticlesCollisionSDF3D *col_sdf = nullptr; - HBoxContainer *bake_hb; - Button *bake; - EditorNode *editor; + HBoxContainer *bake_hb = nullptr; + Button *bake = nullptr; - EditorFileDialog *probe_file; + EditorFileDialog *probe_file = nullptr; static EditorProgress *tmp_progress; static void bake_func_begin(int p_steps); @@ -66,7 +68,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - GPUParticlesCollisionSDF3DEditorPlugin(EditorNode *p_node); + GPUParticlesCollisionSDF3DEditorPlugin(); ~GPUParticlesCollisionSDF3DEditorPlugin(); }; diff --git a/editor/plugins/gradient_editor.cpp b/editor/plugins/gradient_editor.cpp new file mode 100644 index 0000000000..2eb17b3f13 --- /dev/null +++ b/editor/plugins/gradient_editor.cpp @@ -0,0 +1,461 @@ +/**************************************************************************/ +/* gradient_editor.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 "gradient_editor.h" + +#include "core/os/keyboard.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/editor_undo_redo_manager.h" + +void GradientEditor::set_gradient(const Ref<Gradient> &p_gradient) { + gradient = p_gradient; + connect("ramp_changed", callable_mp(this, &GradientEditor::_ramp_changed)); + gradient->connect("changed", callable_mp(this, &GradientEditor::_gradient_changed)); + set_points(gradient->get_points()); + set_interpolation_mode(gradient->get_interpolation_mode()); +} + +void GradientEditor::reverse_gradient() { + gradient->reverse(); + set_points(gradient->get_points()); + emit_signal(SNAME("ramp_changed")); + queue_redraw(); +} + +int GradientEditor::_get_point_from_pos(int x) { + int result = -1; + int total_w = get_size().width - get_size().height - draw_spacing - handle_width; + float min_distance = 1e20; + for (int i = 0; i < points.size(); i++) { + // Check if we clicked at point. + float distance = ABS(x - points[i].offset * total_w); + float min = handle_width * 0.85; // Allow the mouse to be more than half a handle width away for ease of grabbing. + if (distance <= min && distance < min_distance) { + result = i; + min_distance = distance; + } + } + return result; +} + +void GradientEditor::_show_color_picker() { + if (grabbed == -1) { + return; + } + picker->set_pick_color(points[grabbed].color); + Size2 minsize = popup->get_contents_minimum_size(); + bool show_above = false; + if (get_global_position().y + get_size().y + minsize.y > get_viewport_rect().size.y) { + show_above = true; + } + if (show_above) { + popup->set_position(get_screen_position() - Vector2(0, minsize.y)); + } else { + popup->set_position(get_screen_position() + Vector2(0, get_size().y)); + } + popup->popup(); +} + +void GradientEditor::_gradient_changed() { + if (editing) { + return; + } + + editing = true; + Vector<Gradient::Point> grad_points = gradient->get_points(); + set_points(grad_points); + set_interpolation_mode(gradient->get_interpolation_mode()); + queue_redraw(); + editing = false; +} + +void GradientEditor::_ramp_changed() { + editing = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Gradient Edited"), UndoRedo::MERGE_ENDS); + undo_redo->add_do_method(gradient.ptr(), "set_offsets", get_offsets()); + undo_redo->add_do_method(gradient.ptr(), "set_colors", get_colors()); + undo_redo->add_do_method(gradient.ptr(), "set_interpolation_mode", get_interpolation_mode()); + undo_redo->add_undo_method(gradient.ptr(), "set_offsets", gradient->get_offsets()); + undo_redo->add_undo_method(gradient.ptr(), "set_colors", gradient->get_colors()); + undo_redo->add_undo_method(gradient.ptr(), "set_interpolation_mode", gradient->get_interpolation_mode()); + undo_redo->commit_action(); + editing = false; +} + +void GradientEditor::_color_changed(const Color &p_color) { + if (grabbed == -1) { + return; + } + points.write[grabbed].color = p_color; + queue_redraw(); + emit_signal(SNAME("ramp_changed")); +} + +void GradientEditor::set_ramp(const Vector<float> &p_offsets, const Vector<Color> &p_colors) { + ERR_FAIL_COND(p_offsets.size() != p_colors.size()); + points.clear(); + for (int i = 0; i < p_offsets.size(); i++) { + Gradient::Point p; + p.offset = p_offsets[i]; + p.color = p_colors[i]; + points.push_back(p); + } + + points.sort(); + queue_redraw(); +} + +Vector<float> GradientEditor::get_offsets() const { + Vector<float> ret; + for (int i = 0; i < points.size(); i++) { + ret.push_back(points[i].offset); + } + return ret; +} + +Vector<Color> GradientEditor::get_colors() const { + Vector<Color> ret; + for (int i = 0; i < points.size(); i++) { + ret.push_back(points[i].color); + } + return ret; +} + +void GradientEditor::set_points(Vector<Gradient::Point> &p_points) { + if (points.size() != p_points.size()) { + grabbed = -1; + } + points.clear(); + points = p_points; + points.sort(); +} + +Vector<Gradient::Point> &GradientEditor::get_points() { + return points; +} + +void GradientEditor::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) { + interpolation_mode = p_interp_mode; +} + +Gradient::InterpolationMode GradientEditor::get_interpolation_mode() { + return interpolation_mode; +} + +ColorPicker *GradientEditor::get_picker() { + return picker; +} + +PopupPanel *GradientEditor::get_popup() { + return popup; +} + +Size2 GradientEditor::get_minimum_size() const { + return Size2(0, 60) * EDSCALE; +} + +void GradientEditor::gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + + Ref<InputEventKey> k = p_event; + + if (k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && grabbed != -1) { + points.remove_at(grabbed); + grabbed = -1; + grabbing = false; + queue_redraw(); + emit_signal(SNAME("ramp_changed")); + accept_event(); + } + + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid() && mb->is_pressed()) { + float adjusted_mb_x = mb->get_position().x - handle_width / 2; + + // Delete point on right click. + if (mb->get_button_index() == MouseButton::RIGHT) { + grabbed = _get_point_from_pos(adjusted_mb_x); + if (grabbed != -1) { + points.remove_at(grabbed); + grabbed = -1; + grabbing = false; + queue_redraw(); + emit_signal(SNAME("ramp_changed")); + accept_event(); + } + } + + // Hold Alt key to duplicate selected color. + if (mb->get_button_index() == MouseButton::LEFT && mb->is_alt_pressed()) { + grabbed = _get_point_from_pos(adjusted_mb_x); + + if (grabbed != -1) { + int total_w = get_size().width - get_size().height - draw_spacing - handle_width; + Gradient::Point new_point = points[grabbed]; + new_point.offset = CLAMP(adjusted_mb_x / float(total_w), 0, 1); + points.push_back(new_point); + points.sort(); + for (int i = 0; i < points.size(); ++i) { + if (points[i].offset == new_point.offset) { + grabbed = i; + break; + } + } + + emit_signal(SNAME("ramp_changed")); + queue_redraw(); + } + } + + // Select. + if (mb->get_button_index() == MouseButton::LEFT) { + queue_redraw(); + int total_w = get_size().width - get_size().height - draw_spacing - handle_width; + + // Check if color selector was clicked or ramp was double-clicked. + if (adjusted_mb_x > total_w + draw_spacing) { + if (!mb->is_double_click()) { + _show_color_picker(); + } + return; + } else if (mb->is_double_click()) { + grabbed = _get_point_from_pos(adjusted_mb_x); + _show_color_picker(); + accept_event(); + return; + } + + grabbing = true; + grabbed = _get_point_from_pos(adjusted_mb_x); + + // Grab or select. + if (grabbed != -1) { + return; + } + + // Insert point. + Gradient::Point new_point; + new_point.offset = CLAMP(adjusted_mb_x / float(total_w), 0, 1); + new_point.color = gradient->get_color_at_offset(new_point.offset); + + points.push_back(new_point); + points.sort(); + for (int i = 0; i < points.size(); i++) { + if (points[i].offset == new_point.offset) { + grabbed = i; + break; + } + } + + emit_signal(SNAME("ramp_changed")); + } + } + + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { + if (grabbing) { + grabbing = false; + emit_signal(SNAME("ramp_changed")); + } + queue_redraw(); + } + + Ref<InputEventMouseMotion> mm = p_event; + + if (mm.is_valid() && grabbing) { + float adjusted_mm_x = mm->get_position().x - handle_width / 2; + int total_w = get_size().width - get_size().height - draw_spacing - handle_width; + float newofs = CLAMP(adjusted_mm_x / float(total_w), 0, 1); + + // Snap to "round" coordinates if holding Ctrl. + // Be more precise if holding Shift as well. + if (mm->is_ctrl_pressed()) { + newofs = Math::snapped(newofs, mm->is_shift_pressed() ? 0.025 : 0.1); + } else if (mm->is_shift_pressed()) { + // Snap to nearest point if holding just Shift. + const float snap_threshold = 0.03; + float smallest_ofs = snap_threshold; + bool found = false; + int nearest_point = 0; + for (int i = 0; i < points.size(); ++i) { + if (i != grabbed) { + float temp_ofs = ABS(points[i].offset - newofs); + if (temp_ofs < smallest_ofs) { + smallest_ofs = temp_ofs; + nearest_point = i; + if (found) { + break; + } + found = true; + } + } + } + if (found) { + if (points[nearest_point].offset < newofs) { + newofs = points[nearest_point].offset + 0.00001; + } else { + newofs = points[nearest_point].offset - 0.00001; + } + newofs = CLAMP(newofs, 0, 1); + } + } + + bool valid = true; + for (int i = 0; i < points.size(); i++) { + if (points[i].offset == newofs && i != grabbed) { + valid = false; + break; + } + } + + if (!valid || grabbed == -1) { + return; + } + points.write[grabbed].offset = newofs; + + points.sort(); + for (int i = 0; i < points.size(); i++) { + if (points[i].offset == newofs) { + grabbed = i; + break; + } + } + + emit_signal(SNAME("ramp_changed")); + + queue_redraw(); + } +} + +void GradientEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (!picker->is_connected("color_changed", callable_mp(this, &GradientEditor::_color_changed))) { + picker->connect("color_changed", callable_mp(this, &GradientEditor::_color_changed)); + } + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + draw_spacing = BASE_SPACING * get_theme_default_base_scale(); + handle_width = BASE_HANDLE_WIDTH * get_theme_default_base_scale(); + } break; + + case NOTIFICATION_DRAW: { + int w = get_size().x; + int h = get_size().y; + + if (w == 0 || h == 0) { + return; // Safety check. We have division by 'h'. And in any case there is nothing to draw with such size. + } + + int total_w = get_size().width - get_size().height - draw_spacing - handle_width; + + // Draw checker pattern for ramp. + draw_texture_rect(get_theme_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons")), Rect2(handle_width / 2, 0, total_w, h), true); + + // Draw color ramp. + gradient_cache->set_points(points); + gradient_cache->set_interpolation_mode(interpolation_mode); + preview_texture->set_gradient(gradient_cache); + draw_texture_rect(preview_texture, Rect2(handle_width / 2, 0, total_w, h)); + + // Draw borders around color ramp if in focus. + if (has_focus()) { + draw_rect(Rect2(handle_width / 2, 0, total_w, h), Color(1, 1, 1, 0.9), false, 1); + } + + // Draw point markers. + for (int i = 0; i < points.size(); i++) { + Color col = points[i].color.get_v() > 0.5 ? Color(0, 0, 0) : Color(1, 1, 1); + col.a = 0.9; + + draw_line(Vector2(points[i].offset * total_w + handle_width / 2, 0), Vector2(points[i].offset * total_w + handle_width / 2, h / 2), col); + Rect2 rect = Rect2(points[i].offset * total_w, h / 2, handle_width, h / 2); + draw_rect(rect, points[i].color, true); + draw_rect(rect, col, false, 1); + if (grabbed == i) { + const Color focus_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + rect = rect.grow(-1); + if (has_focus()) { + draw_rect(rect, focus_color, false, 1); + } else { + draw_rect(rect, focus_color.darkened(0.4), false, 1); + } + + rect = rect.grow(-1); + draw_rect(rect, col, false, 1); + } + } + + // Draw "button" for color selector. + int button_offset = total_w + handle_width + draw_spacing; + draw_texture_rect(get_theme_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons")), Rect2(button_offset, 0, h, h), true); + if (grabbed != -1) { + // Draw with selection color. + draw_rect(Rect2(button_offset, 0, h, h), points[grabbed].color); + } else { + // If no color selected draw gray color with 'X' on top. + draw_rect(Rect2(button_offset, 0, h, h), Color(0.5, 0.5, 0.5, 1)); + draw_line(Vector2(button_offset, 0), Vector2(button_offset + h, h), Color(1, 1, 1, 0.6)); + draw_line(Vector2(button_offset, h), Vector2(button_offset + h, 0), Color(1, 1, 1, 0.6)); + } + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + grabbing = false; + } + } break; + } +} + +void GradientEditor::_bind_methods() { + ADD_SIGNAL(MethodInfo("ramp_changed")); +} + +GradientEditor::GradientEditor() { + set_focus_mode(FOCUS_ALL); + + popup = memnew(PopupPanel); + picker = memnew(ColorPicker); + popup->add_child(picker); + popup->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(GradientEditor::get_picker())); + + gradient_cache.instantiate(); + preview_texture.instantiate(); + + preview_texture->set_width(1024); + add_child(popup, false, INTERNAL_MODE_FRONT); +} + +GradientEditor::~GradientEditor() { +} diff --git a/editor/plugins/gradient_editor.h b/editor/plugins/gradient_editor.h new file mode 100644 index 0000000000..a8757573d5 --- /dev/null +++ b/editor/plugins/gradient_editor.h @@ -0,0 +1,96 @@ +/**************************************************************************/ +/* gradient_editor.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 GRADIENT_EDITOR_H +#define GRADIENT_EDITOR_H + +#include "scene/gui/color_picker.h" +#include "scene/gui/popup.h" +#include "scene/resources/gradient.h" + +class GradientEditor : public Control { + GDCLASS(GradientEditor, Control); + + PopupPanel *popup = nullptr; + ColorPicker *picker = nullptr; + + bool grabbing = false; + int grabbed = -1; + Vector<Gradient::Point> points; + Gradient::InterpolationMode interpolation_mode = Gradient::GRADIENT_INTERPOLATE_LINEAR; + + bool editing = false; + Ref<Gradient> gradient; + Ref<Gradient> gradient_cache; + Ref<GradientTexture1D> preview_texture; + + // Make sure to use the scaled value below. + const int BASE_SPACING = 3; + const int BASE_HANDLE_WIDTH = 8; + + int draw_spacing = BASE_SPACING; + int handle_width = BASE_HANDLE_WIDTH; + + void _gradient_changed(); + void _ramp_changed(); + void _color_changed(const Color &p_color); + + int _get_point_from_pos(int x); + void _show_color_picker(); + +protected: + virtual void gui_input(const Ref<InputEvent> &p_event) override; + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_gradient(const Ref<Gradient> &p_gradient); + void reverse_gradient(); + + void set_ramp(const Vector<float> &p_offsets, const Vector<Color> &p_colors); + + Vector<float> get_offsets() const; + Vector<Color> get_colors() const; + void set_points(Vector<Gradient::Point> &p_points); + Vector<Gradient::Point> &get_points(); + + void set_interpolation_mode(Gradient::InterpolationMode p_interp_mode); + Gradient::InterpolationMode get_interpolation_mode(); + + ColorPicker *get_picker(); + PopupPanel *get_popup(); + + virtual Size2 get_minimum_size() const override; + + GradientEditor(); + virtual ~GradientEditor(); +}; + +#endif // GRADIENT_EDITOR_H diff --git a/editor/plugins/gradient_editor_plugin.cpp b/editor/plugins/gradient_editor_plugin.cpp index 5e300d3de9..57a7f527ec 100644 --- a/editor/plugins/gradient_editor_plugin.cpp +++ b/editor/plugins/gradient_editor_plugin.cpp @@ -1,102 +1,52 @@ -/*************************************************************************/ -/* gradient_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* gradient_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 "gradient_editor_plugin.h" #include "canvas_item_editor_plugin.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "node_3d_editor_plugin.h" -Size2 GradientEditor::get_minimum_size() const { - return Size2(0, 60) * EDSCALE; -} - -void GradientEditor::_gradient_changed() { - if (editing) { - return; - } - - editing = true; - Vector<Gradient::Point> points = gradient->get_points(); - set_points(points); - set_interpolation_mode(gradient->get_interpolation_mode()); - update(); - editing = false; -} - -void GradientEditor::_ramp_changed() { - editing = true; - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); - undo_redo->create_action(TTR("Gradient Edited")); - undo_redo->add_do_method(gradient.ptr(), "set_offsets", get_offsets()); - undo_redo->add_do_method(gradient.ptr(), "set_colors", get_colors()); - undo_redo->add_do_method(gradient.ptr(), "set_interpolation_mode", get_interpolation_mode()); - undo_redo->add_undo_method(gradient.ptr(), "set_offsets", gradient->get_offsets()); - undo_redo->add_undo_method(gradient.ptr(), "set_colors", gradient->get_colors()); - undo_redo->add_undo_method(gradient.ptr(), "set_interpolation_mode", gradient->get_interpolation_mode()); - undo_redo->commit_action(); - editing = false; -} - -void GradientEditor::_bind_methods() { -} - -void GradientEditor::set_gradient(const Ref<Gradient> &p_gradient) { - gradient = p_gradient; - connect("ramp_changed", callable_mp(this, &GradientEditor::_ramp_changed)); - gradient->connect("changed", callable_mp(this, &GradientEditor::_gradient_changed)); - set_points(gradient->get_points()); - set_interpolation_mode(gradient->get_interpolation_mode()); -} - -void GradientEditor::reverse_gradient() { - gradient->reverse(); - set_points(gradient->get_points()); - emit_signal(SNAME("ramp_changed")); - update(); -} - -GradientEditor::GradientEditor() { - editing = false; -} - -/////////////////////// - void GradientReverseButton::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> icon = get_theme_icon(SNAME("ReverseGradient"), SNAME("EditorIcons")); - if (is_pressed()) { - draw_texture_rect(icon, Rect2(margin, margin, icon->get_width(), icon->get_height()), false, get_theme_color(SNAME("icon_pressed_color"), SNAME("Button"))); - } else { - draw_texture_rect(icon, Rect2(margin, margin, icon->get_width(), icon->get_height())); - } + switch (p_what) { + case NOTIFICATION_DRAW: { + Ref<Texture2D> icon = get_theme_icon(SNAME("ReverseGradient"), SNAME("EditorIcons")); + if (is_pressed()) { + draw_texture_rect(icon, Rect2(margin, margin, icon->get_width(), icon->get_height()), false, get_theme_color(SNAME("icon_pressed_color"), SNAME("Button"))); + } else { + draw_texture_rect(icon, Rect2(margin, margin, icon->get_width(), icon->get_height())); + } + } break; } } @@ -129,14 +79,14 @@ void EditorInspectorPluginGradient::parse_begin(Object *p_object) { add_custom_control(gradient_tools_hbox); reverse_btn->connect("pressed", callable_mp(this, &EditorInspectorPluginGradient::_reverse_button_pressed)); - reverse_btn->set_tooltip(TTR("Reverse/mirror gradient.")); + reverse_btn->set_tooltip_text(TTR("Reverse/mirror gradient.")); } void EditorInspectorPluginGradient::_reverse_button_pressed() { editor->reverse_gradient(); } -GradientEditorPlugin::GradientEditorPlugin(EditorNode *p_node) { +GradientEditorPlugin::GradientEditorPlugin() { Ref<EditorInspectorPluginGradient> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/gradient_editor_plugin.h b/editor/plugins/gradient_editor_plugin.h index 8239711667..f6908f2cbd 100644 --- a/editor/plugins/gradient_editor_plugin.h +++ b/editor/plugins/gradient_editor_plugin.h @@ -1,58 +1,39 @@ -/*************************************************************************/ -/* gradient_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* gradient_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 GRADIENT_EDITOR_PLUGIN_H #define GRADIENT_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/editor_inspector.h" #include "editor/editor_plugin.h" -#include "scene/gui/gradient_edit.h" - -class GradientEditor : public GradientEdit { - GDCLASS(GradientEditor, GradientEdit); - - bool editing; - Ref<Gradient> gradient; - - void _gradient_changed(); - void _ramp_changed(); - -protected: - static void _bind_methods(); - -public: - virtual Size2 get_minimum_size() const override; - void set_gradient(const Ref<Gradient> &p_gradient); - void reverse_gradient(); - GradientEditor(); -}; +#include "gradient_editor.h" class GradientReverseButton : public BaseButton { GDCLASS(GradientReverseButton, BaseButton); @@ -66,9 +47,9 @@ class GradientReverseButton : public BaseButton { class EditorInspectorPluginGradient : public EditorInspectorPlugin { GDCLASS(EditorInspectorPluginGradient, EditorInspectorPlugin); - GradientEditor *editor; - HBoxContainer *gradient_tools_hbox; - GradientReverseButton *reverse_btn; + GradientEditor *editor = nullptr; + HBoxContainer *gradient_tools_hbox = nullptr; + GradientReverseButton *reverse_btn = nullptr; void _reverse_button_pressed(); @@ -83,7 +64,7 @@ class GradientEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "Gradient"; } - GradientEditorPlugin(EditorNode *p_node); + GradientEditorPlugin(); }; #endif // GRADIENT_EDITOR_PLUGIN_H diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.cpp b/editor/plugins/gradient_texture_2d_editor_plugin.cpp new file mode 100644 index 0000000000..7bd159a5b8 --- /dev/null +++ b/editor/plugins/gradient_texture_2d_editor_plugin.cpp @@ -0,0 +1,283 @@ +/**************************************************************************/ +/* gradient_texture_2d_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 "gradient_texture_2d_editor_plugin.h" + +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/editor_undo_redo_manager.h" +#include "scene/gui/box_container.h" +#include "scene/gui/flow_container.h" +#include "scene/gui/separator.h" + +Point2 GradientTexture2DEditorRect::_get_handle_position(const Handle p_handle) { + // Get the handle's mouse position in pixels relative to offset. + return (p_handle == HANDLE_FILL_FROM ? texture->get_fill_from() : texture->get_fill_to()).clamp(Vector2(), Vector2(1, 1)) * size; +} + +void GradientTexture2DEditorRect::_update_fill_position() { + if (handle == HANDLE_NONE) { + return; + } + + // Update the texture's fill_from/fill_to property based on mouse input. + Vector2 percent = ((get_local_mouse_position() - offset) / size).clamp(Vector2(), Vector2(1, 1)); + if (snap_enabled) { + percent = (percent - Vector2(0.5, 0.5)).snapped(Vector2(snap_size, snap_size)) + Vector2(0.5, 0.5); + } + + String property_name = handle == HANDLE_FILL_FROM ? "fill_from" : "fill_to"; + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(vformat(TTR("Set %s"), property_name), UndoRedo::MERGE_ENDS); + undo_redo->add_do_property(texture.ptr(), property_name, percent); + undo_redo->add_undo_property(texture.ptr(), property_name, handle == HANDLE_FILL_FROM ? texture->get_fill_from() : texture->get_fill_to()); + undo_redo->commit_action(); +} + +void GradientTexture2DEditorRect::gui_input(const Ref<InputEvent> &p_event) { + // Grab/release handle. + const Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { + if (mb->is_pressed()) { + Point2 mouse_position = mb->get_position() - offset; + if (Rect2(_get_handle_position(HANDLE_FILL_FROM).round() - handle_size / 2, handle_size).has_point(mouse_position)) { + handle = HANDLE_FILL_FROM; + } else if (Rect2(_get_handle_position(HANDLE_FILL_TO).round() - handle_size / 2, handle_size).has_point(mouse_position)) { + handle = HANDLE_FILL_TO; + } else { + handle = HANDLE_NONE; + } + } else { + _update_fill_position(); + handle = HANDLE_NONE; + } + } + + // Move handle. + const Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + _update_fill_position(); + } +} + +void GradientTexture2DEditorRect::set_texture(Ref<GradientTexture2D> &p_texture) { + texture = p_texture; + texture->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); +} + +void GradientTexture2DEditorRect::set_snap_enabled(bool p_snap_enabled) { + snap_enabled = p_snap_enabled; + queue_redraw(); +} + +void GradientTexture2DEditorRect::set_snap_size(float p_snap_size) { + snap_size = p_snap_size; + queue_redraw(); +} + +void GradientTexture2DEditorRect::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + checkerboard->set_texture(get_theme_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons"))); + } break; + + case NOTIFICATION_DRAW: { + if (texture.is_null()) { + return; + } + + const Ref<Texture2D> fill_from_icon = get_theme_icon(SNAME("EditorPathSmoothHandle"), SNAME("EditorIcons")); + const Ref<Texture2D> fill_to_icon = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons")); + handle_size = fill_from_icon->get_size(); + + Size2 rect_size = get_size(); + + // Get the size and position to draw the texture and handles at. + // Subtract handle sizes so they stay inside the preview, but keep the texture's aspect ratio. + Size2 available_size = rect_size - handle_size; + Size2 ratio = available_size / texture->get_size(); + size = MIN(ratio.x, ratio.y) * texture->get_size(); + offset = ((rect_size - size) / 2).round(); + + checkerboard->set_rect(Rect2(offset, size)); + + draw_set_transform(offset); + draw_texture_rect(texture, Rect2(Point2(), size)); + + // Draw grid snap lines. + if (snap_enabled) { + const Color primary_line_color = Color(0.5, 0.5, 0.5, 0.9); + const Color line_color = Color(0.5, 0.5, 0.5, 0.5); + + // Draw border and centered axis lines. + draw_rect(Rect2(Point2(), size), primary_line_color, false); + draw_line(Point2(size.width / 2, 0), Point2(size.width / 2, size.height), primary_line_color); + draw_line(Point2(0, size.height / 2), Point2(size.width, size.height / 2), primary_line_color); + + // Draw vertical lines. + int prev_idx = 0; + for (int x = 0; x < size.width; x++) { + int idx = int((x / size.width - 0.5) / snap_size); + + if (x > 0 && prev_idx != idx) { + draw_line(Point2(x, 0), Point2(x, size.height), line_color); + } + + prev_idx = idx; + } + + // Draw horizontal lines. + prev_idx = 0; + for (int y = 0; y < size.height; y++) { + int idx = int((y / size.height - 0.5) / snap_size); + + if (y > 0 && prev_idx != idx) { + draw_line(Point2(0, y), Point2(size.width, y), line_color); + } + + prev_idx = idx; + } + } + + // Draw handles. + draw_texture(fill_from_icon, (_get_handle_position(HANDLE_FILL_FROM) - handle_size / 2).round()); + draw_texture(fill_to_icon, (_get_handle_position(HANDLE_FILL_TO) - handle_size / 2).round()); + } break; + } +} + +GradientTexture2DEditorRect::GradientTexture2DEditorRect() { + checkerboard = memnew(TextureRect); + checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE); + checkerboard->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); + checkerboard->set_draw_behind_parent(true); + add_child(checkerboard, false, INTERNAL_MODE_FRONT); + + set_custom_minimum_size(Size2(0, 250 * EDSCALE)); +} + +/////////////////////// + +void GradientTexture2DEditor::_reverse_button_pressed() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Swap GradientTexture2D Fill Points")); + undo_redo->add_do_property(texture.ptr(), "fill_from", texture->get_fill_to()); + undo_redo->add_do_property(texture.ptr(), "fill_to", texture->get_fill_from()); + undo_redo->add_undo_property(texture.ptr(), "fill_from", texture->get_fill_from()); + undo_redo->add_undo_property(texture.ptr(), "fill_to", texture->get_fill_to()); + undo_redo->commit_action(); +} + +void GradientTexture2DEditor::_set_snap_enabled(bool p_enabled) { + texture_editor_rect->set_snap_enabled(p_enabled); + + snap_size_edit->set_visible(p_enabled); +} + +void GradientTexture2DEditor::_set_snap_size(float p_snap_size) { + texture_editor_rect->set_snap_size(MAX(p_snap_size, 0.01)); +} + +void GradientTexture2DEditor::set_texture(Ref<GradientTexture2D> &p_texture) { + texture = p_texture; + texture_editor_rect->set_texture(p_texture); +} + +void GradientTexture2DEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + reverse_button->set_icon(get_theme_icon(SNAME("ReverseGradient"), SNAME("EditorIcons"))); + snap_button->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + } break; + } +} + +GradientTexture2DEditor::GradientTexture2DEditor() { + HFlowContainer *toolbar = memnew(HFlowContainer); + add_child(toolbar); + + reverse_button = memnew(Button); + reverse_button->set_tooltip_text(TTR("Swap Gradient Fill Points")); + toolbar->add_child(reverse_button); + reverse_button->connect("pressed", callable_mp(this, &GradientTexture2DEditor::_reverse_button_pressed)); + + toolbar->add_child(memnew(VSeparator)); + + snap_button = memnew(Button); + snap_button->set_tooltip_text(TTR("Toggle Grid Snap")); + snap_button->set_toggle_mode(true); + toolbar->add_child(snap_button); + snap_button->connect("toggled", callable_mp(this, &GradientTexture2DEditor::_set_snap_enabled)); + + snap_size_edit = memnew(EditorSpinSlider); + snap_size_edit->set_min(0.01); + snap_size_edit->set_max(0.5); + snap_size_edit->set_step(0.01); + snap_size_edit->set_value(0.1); + snap_size_edit->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); + toolbar->add_child(snap_size_edit); + snap_size_edit->connect("value_changed", callable_mp(this, &GradientTexture2DEditor::_set_snap_size)); + + texture_editor_rect = memnew(GradientTexture2DEditorRect); + add_child(texture_editor_rect); + + set_mouse_filter(MOUSE_FILTER_STOP); + _set_snap_enabled(snap_button->is_pressed()); + _set_snap_size(snap_size_edit->get_value()); +} + +/////////////////////// + +bool EditorInspectorPluginGradientTexture2D::can_handle(Object *p_object) { + return Object::cast_to<GradientTexture2D>(p_object) != nullptr; +} + +void EditorInspectorPluginGradientTexture2D::parse_begin(Object *p_object) { + GradientTexture2D *texture = Object::cast_to<GradientTexture2D>(p_object); + if (!texture) { + return; + } + Ref<GradientTexture2D> t(texture); + + GradientTexture2DEditor *editor = memnew(GradientTexture2DEditor); + editor->set_texture(t); + add_custom_control(editor); +} + +/////////////////////// + +GradientTexture2DEditorPlugin::GradientTexture2DEditorPlugin() { + Ref<EditorInspectorPluginGradientTexture2D> plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.h b/editor/plugins/gradient_texture_2d_editor_plugin.h new file mode 100644 index 0000000000..724ec63b4e --- /dev/null +++ b/editor/plugins/gradient_texture_2d_editor_plugin.h @@ -0,0 +1,111 @@ +/**************************************************************************/ +/* gradient_texture_2d_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 GRADIENT_TEXTURE_2D_EDITOR_PLUGIN_H +#define GRADIENT_TEXTURE_2D_EDITOR_PLUGIN_H + +#include "editor/editor_inspector.h" +#include "editor/editor_plugin.h" +#include "editor/editor_spin_slider.h" + +class GradientTexture2DEditorRect : public Control { + GDCLASS(GradientTexture2DEditorRect, Control); + + enum Handle { + HANDLE_NONE, + HANDLE_FILL_FROM, + HANDLE_FILL_TO + }; + + Ref<GradientTexture2D> texture; + bool snap_enabled = false; + float snap_size = 0; + + TextureRect *checkerboard = nullptr; + + Handle handle = HANDLE_NONE; + Size2 handle_size; + Point2 offset; + Size2 size; + + Point2 _get_handle_position(const Handle p_handle); + void _update_fill_position(); + virtual void gui_input(const Ref<InputEvent> &p_event) override; + +protected: + void _notification(int p_what); + +public: + void set_texture(Ref<GradientTexture2D> &p_texture); + void set_snap_enabled(bool p_snap_enabled); + void set_snap_size(float p_snap_size); + + GradientTexture2DEditorRect(); +}; + +class GradientTexture2DEditor : public VBoxContainer { + GDCLASS(GradientTexture2DEditor, VBoxContainer); + + Ref<GradientTexture2D> texture; + + Button *reverse_button = nullptr; + Button *snap_button = nullptr; + EditorSpinSlider *snap_size_edit = nullptr; + GradientTexture2DEditorRect *texture_editor_rect = nullptr; + + void _reverse_button_pressed(); + void _set_snap_enabled(bool p_enabled); + void _set_snap_size(float p_snap_size); + +protected: + void _notification(int p_what); + +public: + void set_texture(Ref<GradientTexture2D> &p_texture); + + GradientTexture2DEditor(); +}; + +class EditorInspectorPluginGradientTexture2D : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginGradientTexture2D, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; +}; + +class GradientTexture2DEditorPlugin : public EditorPlugin { + GDCLASS(GradientTexture2DEditorPlugin, EditorPlugin); + +public: + GradientTexture2DEditorPlugin(); +}; + +#endif // GRADIENT_TEXTURE_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/input_event_editor_plugin.cpp b/editor/plugins/input_event_editor_plugin.cpp index b0ee88479a..be36447432 100644 --- a/editor/plugins/input_event_editor_plugin.cpp +++ b/editor/plugins/input_event_editor_plugin.cpp @@ -1,38 +1,50 @@ -/*************************************************************************/ -/* input_event_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* input_event_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 "input_event_editor_plugin.h" +#include "editor/event_listener_line_edit.h" +#include "editor/input_event_configuration_dialog.h" + void InputEventConfigContainer::_bind_methods() { } +void InputEventConfigContainer::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + open_config_button->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + } break; + } +} + void InputEventConfigContainer::_configure_pressed() { config_dialog->popup_and_configure(input_event); } @@ -47,12 +59,6 @@ void InputEventConfigContainer::_config_dialog_confirmed() { _event_changed(); } -Size2 InputEventConfigContainer::get_minimum_size() const { - // Don't bother with a minimum x size for the control - we don't want the inspector - // to jump in size if a long text is placed in the label (e.g. Joypad Axis description) - return Size2(0, HBoxContainer::get_minimum_size().y); -} - void InputEventConfigContainer::set_event(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; Ref<InputEventMouseButton> m = p_event; @@ -60,13 +66,13 @@ void InputEventConfigContainer::set_event(const Ref<InputEvent> &p_event) { Ref<InputEventJoypadMotion> jm = p_event; if (k.is_valid()) { - config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_KEY); + config_dialog->set_allowed_input_types(INPUT_KEY); } else if (m.is_valid()) { - config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_MOUSE_BUTTON); + config_dialog->set_allowed_input_types(INPUT_MOUSE_BUTTON); } else if (jb.is_valid()) { - config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_JOY_BUTTON); + config_dialog->set_allowed_input_types(INPUT_JOY_BUTTON); } else if (jm.is_valid()) { - config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_JOY_MOTION); + config_dialog->set_allowed_input_types(INPUT_JOY_MOTION); } input_event = p_event; @@ -75,29 +81,25 @@ void InputEventConfigContainer::set_event(const Ref<InputEvent> &p_event) { } InputEventConfigContainer::InputEventConfigContainer() { - MarginContainer *mc = memnew(MarginContainer); - mc->add_theme_constant_override("margin_left", 10); - mc->add_theme_constant_override("margin_right", 10); - mc->add_theme_constant_override("margin_top", 10); - mc->add_theme_constant_override("margin_bottom", 10); - add_child(mc); - - HBoxContainer *hb = memnew(HBoxContainer); - mc->add_child(hb); - - open_config_button = memnew(Button); - open_config_button->set_text(TTR("Configure")); + input_event_text = memnew(Label); + input_event_text->set_h_size_flags(SIZE_EXPAND_FILL); + input_event_text->set_autowrap_mode(TextServer::AutowrapMode::AUTOWRAP_WORD_SMART); + input_event_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + add_child(input_event_text); + + open_config_button = EditorInspector::create_inspector_action_button(TTR("Configure")); open_config_button->connect("pressed", callable_mp(this, &InputEventConfigContainer::_configure_pressed)); - hb->add_child(open_config_button); + add_child(open_config_button); - input_event_text = memnew(Label); - hb->add_child(input_event_text); + add_child(memnew(Control)); config_dialog = memnew(InputEventConfigurationDialog); config_dialog->connect("confirmed", callable_mp(this, &InputEventConfigContainer::_config_dialog_confirmed)); add_child(config_dialog); } +/////////////////////// + bool EditorInspectorPluginInputEvent::can_handle(Object *p_object) { Ref<InputEventKey> k = Ref<InputEventKey>(p_object); Ref<InputEventMouseButton> m = Ref<InputEventMouseButton>(p_object); @@ -115,7 +117,9 @@ void EditorInspectorPluginInputEvent::parse_begin(Object *p_object) { add_custom_control(picker_controls); } -InputEventEditorPlugin::InputEventEditorPlugin(EditorNode *p_node) { +/////////////////////// + +InputEventEditorPlugin::InputEventEditorPlugin() { Ref<EditorInspectorPluginInputEvent> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/input_event_editor_plugin.h b/editor/plugins/input_event_editor_plugin.h index ed26890229..779e59edd4 100644 --- a/editor/plugins/input_event_editor_plugin.h +++ b/editor/plugins/input_event_editor_plugin.h @@ -1,48 +1,48 @@ -/*************************************************************************/ -/* input_event_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* input_event_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 INPUT_EVENT_EDITOR_PLUGIN_H #define INPUT_EVENT_EDITOR_PLUGIN_H #include "editor/action_map_editor.h" #include "editor/editor_inspector.h" -#include "editor/editor_node.h" +#include "editor/editor_plugin.h" -class InputEventConfigContainer : public HBoxContainer { - GDCLASS(InputEventConfigContainer, HBoxContainer); +class InputEventConfigContainer : public VBoxContainer { + GDCLASS(InputEventConfigContainer, VBoxContainer); - Label *input_event_text; - Button *open_config_button; + Label *input_event_text = nullptr; + Button *open_config_button = nullptr; Ref<InputEvent> input_event; - InputEventConfigurationDialog *config_dialog; + InputEventConfigurationDialog *config_dialog = nullptr; void _config_dialog_confirmed(); void _configure_pressed(); @@ -50,10 +50,10 @@ class InputEventConfigContainer : public HBoxContainer { void _event_changed(); protected: + void _notification(int p_what); static void _bind_methods(); public: - virtual Size2 get_minimum_size() const override; void set_event(const Ref<InputEvent> &p_event); InputEventConfigContainer(); @@ -73,7 +73,7 @@ class InputEventEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "InputEvent"; } - InputEventEditorPlugin(EditorNode *p_node); + InputEventEditorPlugin(); }; #endif // INPUT_EVENT_EDITOR_PLUGIN_H diff --git a/editor/plugins/light_occluder_2d_editor_plugin.cpp b/editor/plugins/light_occluder_2d_editor_plugin.cpp index 94ab89e2f6..429add4540 100644 --- a/editor/plugins/light_occluder_2d_editor_plugin.cpp +++ b/editor/plugins/light_occluder_2d_editor_plugin.cpp @@ -1,35 +1,38 @@ -/*************************************************************************/ -/* light_occluder_2d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* light_occluder_2d_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 "light_occluder_2d_editor_plugin.h" +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" + Ref<OccluderPolygon2D> LightOccluder2DEditor::_ensure_occluder() const { Ref<OccluderPolygon2D> occluder = node->get_occluder_polygon(); if (!occluder.is_valid()) { @@ -81,6 +84,7 @@ void LightOccluder2DEditor::_set_polygon(int p_idx, const Variant &p_polygon) co void LightOccluder2DEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) { Ref<OccluderPolygon2D> occluder = _ensure_occluder(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->add_do_method(occluder.ptr(), "set_polygon", p_polygon); undo_redo->add_undo_method(occluder.ptr(), "set_polygon", p_previous); } @@ -94,19 +98,17 @@ void LightOccluder2DEditor::_create_resource() { return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Create Occluder Polygon")); undo_redo->add_do_method(node, "set_occluder_polygon", Ref<OccluderPolygon2D>(memnew(OccluderPolygon2D))); - undo_redo->add_undo_method(node, "set_occluder_polygon", Variant(REF())); + undo_redo->add_undo_method(node, "set_occluder_polygon", Variant(Ref<RefCounted>())); undo_redo->commit_action(); _menu_option(MODE_CREATE); } -LightOccluder2DEditor::LightOccluder2DEditor(EditorNode *p_editor) : - AbstractPolygon2DEditor(p_editor) { - node = nullptr; -} +LightOccluder2DEditor::LightOccluder2DEditor() {} -LightOccluder2DEditorPlugin::LightOccluder2DEditorPlugin(EditorNode *p_node) : - AbstractPolygon2DEditorPlugin(p_node, memnew(LightOccluder2DEditor(p_node)), "LightOccluder2D") { +LightOccluder2DEditorPlugin::LightOccluder2DEditorPlugin() : + AbstractPolygon2DEditorPlugin(memnew(LightOccluder2DEditor), "LightOccluder2D") { } diff --git a/editor/plugins/light_occluder_2d_editor_plugin.h b/editor/plugins/light_occluder_2d_editor_plugin.h index 1a0cd3514b..01c8a185b6 100644 --- a/editor/plugins/light_occluder_2d_editor_plugin.h +++ b/editor/plugins/light_occluder_2d_editor_plugin.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* light_occluder_2d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* light_occluder_2d_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 LIGHT_OCCLUDER_2D_EDITOR_PLUGIN_H #define LIGHT_OCCLUDER_2D_EDITOR_PLUGIN_H @@ -37,7 +37,7 @@ class LightOccluder2DEditor : public AbstractPolygon2DEditor { GDCLASS(LightOccluder2DEditor, AbstractPolygon2DEditor); - LightOccluder2D *node; + LightOccluder2D *node = nullptr; Ref<OccluderPolygon2D> _ensure_occluder() const; @@ -56,14 +56,14 @@ protected: virtual void _create_resource() override; public: - LightOccluder2DEditor(EditorNode *p_editor); + LightOccluder2DEditor(); }; class LightOccluder2DEditorPlugin : public AbstractPolygon2DEditorPlugin { GDCLASS(LightOccluder2DEditorPlugin, AbstractPolygon2DEditorPlugin); public: - LightOccluder2DEditorPlugin(EditorNode *p_node); + LightOccluder2DEditorPlugin(); }; #endif // LIGHT_OCCLUDER_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index be17fc238a..519cfcaa94 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -1,45 +1,80 @@ -/*************************************************************************/ -/* lightmap_gi_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* lightmap_gi_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 "lightmap_gi_editor_plugin.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" + void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { if (lightmap) { - LightmapGI::BakeError err; - if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == lightmap) { - err = lightmap->bake(lightmap, p_file, bake_func_step); + LightmapGI::BakeError err = LightmapGI::BAKE_ERROR_OK; + const uint64_t time_started = OS::get_singleton()->get_ticks_msec(); + if (get_tree()->get_edited_scene_root()) { + Ref<LightmapGIData> lightmapGIData = lightmap->get_light_data(); + + if (lightmapGIData.is_valid()) { + String path = lightmapGIData->get_path(); + if (!path.is_resource_file()) { + int srpos = path.find("::"); + if (srpos != -1) { + String base = path.substr(0, srpos); + if (ResourceLoader::get_resource_type(base) == "PackedScene") { + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + err = LightmapGI::BAKE_ERROR_FOREIGN_DATA; + } + } else { + if (FileAccess::exists(base + ".import")) { + err = LightmapGI::BAKE_ERROR_FOREIGN_DATA; + } + } + } + } else { + if (FileAccess::exists(path + ".import")) { + err = LightmapGI::BAKE_ERROR_FOREIGN_DATA; + } + } + } + + if (err == LightmapGI::BAKE_ERROR_OK) { + if (get_tree()->get_edited_scene_root() == lightmap) { + err = lightmap->bake(lightmap, p_file, bake_func_step); + } else { + err = lightmap->bake(lightmap->get_parent(), p_file, bake_func_step); + } + } } else { - err = lightmap->bake(lightmap->get_parent(), p_file, bake_func_step); + err = LightmapGI::BAKE_ERROR_NO_SCENE_ROOT; } - bake_func_end(); + bake_func_end(time_started); switch (err) { case LightmapGI::BAKE_ERROR_NO_SAVE_PATH: { @@ -55,16 +90,21 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { file_dialog->set_current_path(scene_path); file_dialog->popup_file_dialog(); - } break; - case LightmapGI::BAKE_ERROR_NO_MESHES: + case LightmapGI::BAKE_ERROR_NO_MESHES: { EditorNode::get_singleton()->show_warning(TTR("No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on.")); - break; - case LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE: + } break; + case LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE: { EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images, make sure path is writable.")); - break; + } break; + case LightmapGI::BAKE_ERROR_NO_SCENE_ROOT: { + EditorNode::get_singleton()->show_warning(TTR("No editor scene root found.")); + } break; + case LightmapGI::BAKE_ERROR_FOREIGN_DATA: { + EditorNode::get_singleton()->show_warning(TTR("Lightmap data is not local to the scene.")); + } break; default: { - } + } break; } } } @@ -104,22 +144,28 @@ bool LightmapGIEditorPlugin::bake_func_step(float p_progress, const String &p_de return tmp_progress->step(p_description, p_progress * 1000, p_refresh); } -void LightmapGIEditorPlugin::bake_func_end() { +void LightmapGIEditorPlugin::bake_func_end(uint64_t p_time_started) { if (tmp_progress != nullptr) { memdelete(tmp_progress); tmp_progress = nullptr; } + + const int time_taken = (OS::get_singleton()->get_ticks_msec() - p_time_started) * 0.001; + print_line(vformat("Done baking lightmaps in %02d:%02d:%02d.", time_taken / 3600, (time_taken % 3600) / 60, time_taken % 60)); + // Request attention in case the user was doing something else. + // Baking lightmaps is likely the editor task that can take the most time, + // so only request the attention for baking lightmaps. + DisplayServer::get_singleton()->window_request_attention(); } void LightmapGIEditorPlugin::_bind_methods() { ClassDB::bind_method("_bake", &LightmapGIEditorPlugin::_bake); } -LightmapGIEditorPlugin::LightmapGIEditorPlugin(EditorNode *p_node) { - editor = p_node; +LightmapGIEditorPlugin::LightmapGIEditorPlugin() { bake = memnew(Button); bake->set_flat(true); - bake->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); + bake->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); bake->set_text(TTR("Bake Lightmaps")); bake->hide(); bake->connect("pressed", Callable(this, "_bake")); @@ -128,7 +174,7 @@ LightmapGIEditorPlugin::LightmapGIEditorPlugin(EditorNode *p_node) { file_dialog = memnew(EditorFileDialog); file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); - file_dialog->add_filter("*.lmbake ; LightMap Bake"); + file_dialog->add_filter("*.lmbake", TTR("LightMap Bake")); file_dialog->set_title(TTR("Select lightmap bake file:")); file_dialog->connect("file_selected", callable_mp(this, &LightmapGIEditorPlugin::_bake_select_file)); bake->add_child(file_dialog); diff --git a/editor/plugins/lightmap_gi_editor_plugin.h b/editor/plugins/lightmap_gi_editor_plugin.h index d0edf9f324..1234438c8d 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.h +++ b/editor/plugins/lightmap_gi_editor_plugin.h @@ -1,53 +1,54 @@ -/*************************************************************************/ -/* lightmap_gi_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* lightmap_gi_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 BAKED_LIGHTMAP_EDITOR_PLUGIN_H -#define BAKED_LIGHTMAP_EDITOR_PLUGIN_H +#ifndef LIGHTMAP_GI_EDITOR_PLUGIN_H +#define LIGHTMAP_GI_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/lightmap_gi.h" #include "scene/resources/material.h" +struct EditorProgress; +class EditorFileDialog; + class LightmapGIEditorPlugin : public EditorPlugin { GDCLASS(LightmapGIEditorPlugin, EditorPlugin); - LightmapGI *lightmap; + LightmapGI *lightmap = nullptr; - Button *bake; - EditorNode *editor; + Button *bake = nullptr; - EditorFileDialog *file_dialog; + EditorFileDialog *file_dialog = nullptr; static EditorProgress *tmp_progress; static bool bake_func_step(float p_progress, const String &p_description, void *, bool p_refresh); - static void bake_func_end(); + static void bake_func_end(uint64_t p_time_started); void _bake_select_file(const String &p_file); void _bake(); @@ -62,8 +63,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - LightmapGIEditorPlugin(EditorNode *p_node); + LightmapGIEditorPlugin(); ~LightmapGIEditorPlugin(); }; -#endif +#endif // LIGHTMAP_GI_EDITOR_PLUGIN_H diff --git a/editor/plugins/line_2d_editor_plugin.cpp b/editor/plugins/line_2d_editor_plugin.cpp index 9d7e22278e..0185617c36 100644 --- a/editor/plugins/line_2d_editor_plugin.cpp +++ b/editor/plugins/line_2d_editor_plugin.cpp @@ -1,35 +1,38 @@ -/*************************************************************************/ -/* line_2d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* line_2d_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 "line_2d_editor_plugin.h" +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" + Node2D *Line2DEditor::_get_node() const { return node; } @@ -51,16 +54,14 @@ 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(); - undo_redo->add_do_method(node, "set_points", p_polygon); - undo_redo->add_undo_method(node, "set_points", p_previous); + Node2D *_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); } -Line2DEditor::Line2DEditor(EditorNode *p_editor) : - AbstractPolygon2DEditor(p_editor) { - node = nullptr; -} +Line2DEditor::Line2DEditor() {} -Line2DEditorPlugin::Line2DEditorPlugin(EditorNode *p_node) : - AbstractPolygon2DEditorPlugin(p_node, memnew(Line2DEditor(p_node)), "Line2D") { +Line2DEditorPlugin::Line2DEditorPlugin() : + AbstractPolygon2DEditorPlugin(memnew(Line2DEditor), "Line2D") { } diff --git a/editor/plugins/line_2d_editor_plugin.h b/editor/plugins/line_2d_editor_plugin.h index 4497307747..f91fec80dd 100644 --- a/editor/plugins/line_2d_editor_plugin.h +++ b/editor/plugins/line_2d_editor_plugin.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* line_2d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* line_2d_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 LINE_2D_EDITOR_PLUGIN_H #define LINE_2D_EDITOR_PLUGIN_H @@ -37,7 +37,7 @@ class Line2DEditor : public AbstractPolygon2DEditor { GDCLASS(Line2DEditor, AbstractPolygon2DEditor); - Line2D *node; + Line2D *node = nullptr; protected: virtual Node2D *_get_node() const override; @@ -49,14 +49,14 @@ protected: virtual void _action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) override; public: - Line2DEditor(EditorNode *p_editor); + Line2DEditor(); }; class Line2DEditorPlugin : public AbstractPolygon2DEditorPlugin { GDCLASS(Line2DEditorPlugin, AbstractPolygon2DEditorPlugin); public: - Line2DEditorPlugin(EditorNode *p_node); + Line2DEditorPlugin(); }; #endif // LINE_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/material_editor_plugin.cpp b/editor/plugins/material_editor_plugin.cpp index 576e91e544..36c143ca8d 100644 --- a/editor/plugins/material_editor_plugin.cpp +++ b/editor/plugins/material_editor_plugin.cpp @@ -1,70 +1,102 @@ -/*************************************************************************/ -/* material_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* material_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 "material_editor_plugin.h" +#include "core/config/project_settings.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "scene/gui/subviewport_container.h" #include "scene/resources/fog_material.h" -#include "scene/resources/particles_material.h" +#include "scene/resources/particle_process_material.h" #include "scene/resources/sky_material.h" -void MaterialEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - //get_scene()->connect("node_removed",this,"_node_removed"); +void MaterialEditor::gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); - if (first_enter) { - //it's in propertyeditor so.. could be moved around + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { + rot.x -= mm->get_relative().y * 0.01; + rot.y -= mm->get_relative().x * 0.01; - light_1_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight1"), SNAME("EditorIcons"))); - light_1_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight1Off"), SNAME("EditorIcons"))); - light_2_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight2"), SNAME("EditorIcons"))); - light_2_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight2Off"), SNAME("EditorIcons"))); + rot.x = CLAMP(rot.x, -Math_PI / 2, Math_PI / 2); + _update_rotation(); + } +} - sphere_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewSphereOff"), SNAME("EditorIcons"))); - sphere_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewSphere"), SNAME("EditorIcons"))); - box_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewCubeOff"), SNAME("EditorIcons"))); - box_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewCube"), SNAME("EditorIcons"))); +void MaterialEditor::_update_theme_item_cache() { + Control::_update_theme_item_cache(); - first_enter = false; - } - } + theme_cache.light_1_on = get_theme_icon(SNAME("MaterialPreviewLight1"), SNAME("EditorIcons")); + theme_cache.light_1_off = get_theme_icon(SNAME("MaterialPreviewLight1Off"), SNAME("EditorIcons")); + theme_cache.light_2_on = get_theme_icon(SNAME("MaterialPreviewLight2"), SNAME("EditorIcons")); + theme_cache.light_2_off = get_theme_icon(SNAME("MaterialPreviewLight2Off"), SNAME("EditorIcons")); - if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); - Size2 size = get_size(); + theme_cache.sphere_on = get_theme_icon(SNAME("MaterialPreviewSphere"), SNAME("EditorIcons")); + theme_cache.sphere_off = get_theme_icon(SNAME("MaterialPreviewSphereOff"), SNAME("EditorIcons")); + theme_cache.box_on = get_theme_icon(SNAME("MaterialPreviewCube"), SNAME("EditorIcons")); + theme_cache.box_off = get_theme_icon(SNAME("MaterialPreviewCubeOff"), SNAME("EditorIcons")); - draw_texture_rect(checkerboard, Rect2(Point2(), size), true); + theme_cache.checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); +} + +void MaterialEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + light_1_switch->set_texture_normal(theme_cache.light_1_on); + light_1_switch->set_texture_pressed(theme_cache.light_1_off); + light_2_switch->set_texture_normal(theme_cache.light_2_on); + light_2_switch->set_texture_pressed(theme_cache.light_2_off); + + sphere_switch->set_texture_normal(theme_cache.sphere_off); + sphere_switch->set_texture_pressed(theme_cache.sphere_on); + box_switch->set_texture_normal(theme_cache.box_off); + box_switch->set_texture_pressed(theme_cache.box_on); + } break; + + case NOTIFICATION_DRAW: { + Size2 size = get_size(); + draw_texture_rect(theme_cache.checkerboard, Rect2(Point2(), size), true); + } break; } } +void MaterialEditor::_update_rotation() { + Transform3D t; + t.basis.rotate(Vector3(0, 1, 0), -rot.y); + t.basis.rotate(Vector3(1, 0, 0), -rot.x); + rotation->set_transform(t); +} + void MaterialEditor::edit(Ref<Material> p_material, const Ref<Environment> &p_env) { material = p_material; camera->set_environment(p_env); @@ -90,6 +122,10 @@ void MaterialEditor::edit(Ref<Material> p_material, const Ref<Environment> &p_en } else { hide(); } + + rot.x = Math::deg_to_rad(-15.0); + rot.y = Math::deg_to_rad(30.0); + _update_rotation(); } void MaterialEditor::_button_pressed(Node *p_button) { @@ -118,16 +154,13 @@ void MaterialEditor::_button_pressed(Node *p_button) { } } -void MaterialEditor::_bind_methods() { -} - MaterialEditor::MaterialEditor() { // canvas item layout_2d = memnew(HBoxContainer); layout_2d->set_alignment(BoxContainer::ALIGNMENT_CENTER); add_child(layout_2d); - layout_2d->set_anchors_and_offsets_preset(PRESET_WIDE); + layout_2d->set_anchors_and_offsets_preset(PRESET_FULL_RECT); rect_instance = memnew(ColorRect); layout_2d->add_child(rect_instance); @@ -140,7 +173,7 @@ MaterialEditor::MaterialEditor() { vc = memnew(SubViewportContainer); vc->set_stretch(true); add_child(vc); - vc->set_anchors_and_offsets_preset(PRESET_WIDE); + vc->set_anchors_and_offsets_preset(PRESET_FULL_RECT); viewport = memnew(SubViewport); Ref<World3D> world_3d; world_3d.instantiate(); @@ -148,12 +181,18 @@ MaterialEditor::MaterialEditor() { vc->add_child(viewport); viewport->set_disable_input(true); viewport->set_transparent_background(true); - viewport->set_msaa(Viewport::MSAA_4X); + viewport->set_msaa_3d(Viewport::MSAA_4X); camera = memnew(Camera3D); - camera->set_transform(Transform3D(Basis(), Vector3(0, 0, 3))); - camera->set_perspective(45, 0.1, 10); + camera->set_transform(Transform3D(Basis(), Vector3(0, 0, 1.1))); + // Use low field of view so the sphere/box is fully encompassed within the preview, + // without much distortion. + camera->set_perspective(20, 0.1, 10); camera->make_current(); + if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { + camera_attributes.instantiate(); + camera->set_attributes(camera_attributes); + } viewport->add_child(camera); light1 = memnew(DirectionalLight3D); @@ -165,18 +204,17 @@ MaterialEditor::MaterialEditor() { light2->set_color(Color(0.7, 0.7, 0.7)); viewport->add_child(light2); + rotation = memnew(Node3D); + viewport->add_child(rotation); + sphere_instance = memnew(MeshInstance3D); - viewport->add_child(sphere_instance); + rotation->add_child(sphere_instance); box_instance = memnew(MeshInstance3D); - viewport->add_child(box_instance); + rotation->add_child(box_instance); - Transform3D box_xform; - box_xform.basis.rotate(Vector3(1, 0, 0), Math::deg2rad(25.0)); - box_xform.basis = box_xform.basis * Basis().rotated(Vector3(0, 1, 0), Math::deg2rad(-25.0)); - box_xform.basis.scale(Vector3(0.8, 0.8, 0.8)); - box_xform.origin.y = 0.2; - box_instance->set_transform(box_xform); + box_instance->set_transform(Transform3D() * 0.25); + sphere_instance->set_transform(Transform3D() * 0.375); sphere_mesh.instantiate(); sphere_instance->set_mesh(sphere_mesh); @@ -187,7 +225,7 @@ MaterialEditor::MaterialEditor() { layout_3d = memnew(HBoxContainer); add_child(layout_3d); - layout_3d->set_anchors_and_offsets_preset(Control::PRESET_WIDE, Control::PRESET_MODE_MINSIZE, 2); + layout_3d->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 2); VBoxContainer *vb_shape = memnew(VBoxContainer); layout_3d->add_child(vb_shape); @@ -196,13 +234,13 @@ MaterialEditor::MaterialEditor() { sphere_switch->set_toggle_mode(true); sphere_switch->set_pressed(true); vb_shape->add_child(sphere_switch); - sphere_switch->connect("pressed", callable_mp(this, &MaterialEditor::_button_pressed), varray(sphere_switch)); + sphere_switch->connect("pressed", callable_mp(this, &MaterialEditor::_button_pressed).bind(sphere_switch)); box_switch = memnew(TextureButton); box_switch->set_toggle_mode(true); box_switch->set_pressed(false); vb_shape->add_child(box_switch); - box_switch->connect("pressed", callable_mp(this, &MaterialEditor::_button_pressed), varray(box_switch)); + box_switch->connect("pressed", callable_mp(this, &MaterialEditor::_button_pressed).bind(box_switch)); layout_3d->add_spacer(); @@ -212,14 +250,12 @@ MaterialEditor::MaterialEditor() { light_1_switch = memnew(TextureButton); light_1_switch->set_toggle_mode(true); vb_light->add_child(light_1_switch); - light_1_switch->connect("pressed", callable_mp(this, &MaterialEditor::_button_pressed), varray(light_1_switch)); + light_1_switch->connect("pressed", callable_mp(this, &MaterialEditor::_button_pressed).bind(light_1_switch)); light_2_switch = memnew(TextureButton); light_2_switch->set_toggle_mode(true); vb_light->add_child(light_2_switch); - light_2_switch->connect("pressed", callable_mp(this, &MaterialEditor::_button_pressed), varray(light_2_switch)); - - first_enter = true; + light_2_switch->connect("pressed", callable_mp(this, &MaterialEditor::_button_pressed).bind(light_2_switch)); if (EditorSettings::get_singleton()->get_project_metadata("inspector_options", "material_preview_on_sphere", true)) { box_instance->hide(); @@ -254,6 +290,41 @@ void EditorInspectorPluginMaterial::parse_begin(Object *p_object) { add_custom_control(editor); } +void EditorInspectorPluginMaterial::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { + EditorUndoRedoManager *undo_redo = Object::cast_to<EditorUndoRedoManager>(p_undo_redo); + ERR_FAIL_NULL(undo_redo); + + // For BaseMaterial3D, if a roughness or metallic textures is being assigned to an empty slot, + // set the respective metallic or roughness factor to 1.0 as a convenience feature + BaseMaterial3D *base_material = Object::cast_to<StandardMaterial3D>(p_edited); + if (base_material) { + Texture2D *texture = Object::cast_to<Texture2D>(p_new_value); + if (texture) { + if (p_property == "roughness_texture") { + if (base_material->get_texture(StandardMaterial3D::TEXTURE_ROUGHNESS).is_null()) { + undo_redo->add_do_property(p_edited, "roughness", 1.0); + + bool valid = false; + Variant value = p_edited->get("roughness", &valid); + if (valid) { + undo_redo->add_undo_property(p_edited, "roughness", value); + } + } + } else if (p_property == "metallic_texture") { + if (base_material->get_texture(StandardMaterial3D::TEXTURE_METALLIC).is_null()) { + undo_redo->add_do_property(p_edited, "metallic", 1.0); + + bool valid = false; + Variant value = p_edited->get("metallic", &valid); + if (valid) { + undo_redo->add_undo_property(p_edited, "metallic", value); + } + } + } + } + } +} + EditorInspectorPluginMaterial::EditorInspectorPluginMaterial() { env.instantiate(); Ref<Sky> sky = memnew(Sky()); @@ -261,9 +332,11 @@ EditorInspectorPluginMaterial::EditorInspectorPluginMaterial() { env->set_background(Environment::BG_COLOR); env->set_ambient_source(Environment::AMBIENT_SOURCE_SKY); env->set_reflection_source(Environment::REFLECTION_SOURCE_SKY); + + EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &EditorInspectorPluginMaterial::_undo_redo_inspector_callback)); } -MaterialEditorPlugin::MaterialEditorPlugin(EditorNode *p_node) { +MaterialEditorPlugin::MaterialEditorPlugin() { Ref<EditorInspectorPluginMaterial> plugin; plugin.instantiate(); add_inspector_plugin(plugin); @@ -295,17 +368,17 @@ Ref<Resource> StandardMaterial3DConversionPlugin::convert(const Ref<Resource> &p smat->set_shader(shader); List<PropertyInfo> params; - RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), ¶ms); + RS::get_singleton()->get_shader_parameter_list(mat->get_shader_rid(), ¶ms); for (const PropertyInfo &E : params) { // Texture parameter has to be treated specially since StandardMaterial3D saved it // as RID but ShaderMaterial needs Texture itself Ref<Texture2D> texture = mat->get_texture_by_name(E.name); if (texture.is_valid()) { - smat->set_shader_param(E.name, texture); + smat->set_shader_parameter(E.name, texture); } else { Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name); - smat->set_shader_param(E.name, value); + smat->set_shader_parameter(E.name, value); } } @@ -341,17 +414,17 @@ Ref<Resource> ORMMaterial3DConversionPlugin::convert(const Ref<Resource> &p_reso smat->set_shader(shader); List<PropertyInfo> params; - RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), ¶ms); + RS::get_singleton()->get_shader_parameter_list(mat->get_shader_rid(), ¶ms); for (const PropertyInfo &E : params) { // Texture parameter has to be treated specially since ORMMaterial3D saved it // as RID but ShaderMaterial needs Texture itself Ref<Texture2D> texture = mat->get_texture_by_name(E.name); if (texture.is_valid()) { - smat->set_shader_param(E.name, texture); + smat->set_shader_parameter(E.name, texture); } else { Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name); - smat->set_shader_param(E.name, value); + smat->set_shader_parameter(E.name, value); } } @@ -361,17 +434,17 @@ Ref<Resource> ORMMaterial3DConversionPlugin::convert(const Ref<Resource> &p_reso return smat; } -String ParticlesMaterialConversionPlugin::converts_to() const { +String ParticleProcessMaterialConversionPlugin::converts_to() const { return "ShaderMaterial"; } -bool ParticlesMaterialConversionPlugin::handles(const Ref<Resource> &p_resource) const { - Ref<ParticlesMaterial> mat = p_resource; +bool ParticleProcessMaterialConversionPlugin::handles(const Ref<Resource> &p_resource) const { + Ref<ParticleProcessMaterial> mat = p_resource; return mat.is_valid(); } -Ref<Resource> ParticlesMaterialConversionPlugin::convert(const Ref<Resource> &p_resource) const { - Ref<ParticlesMaterial> mat = p_resource; +Ref<Resource> ParticleProcessMaterialConversionPlugin::convert(const Ref<Resource> &p_resource) const { + Ref<ParticleProcessMaterial> mat = p_resource; ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>()); Ref<ShaderMaterial> smat; @@ -387,11 +460,11 @@ Ref<Resource> ParticlesMaterialConversionPlugin::convert(const Ref<Resource> &p_ smat->set_shader(shader); List<PropertyInfo> params; - RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), ¶ms); + RS::get_singleton()->get_shader_parameter_list(mat->get_shader_rid(), ¶ms); for (const PropertyInfo &E : params) { Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name); - smat->set_shader_param(E.name, value); + smat->set_shader_parameter(E.name, value); } smat->set_render_priority(mat->get_render_priority()); @@ -426,11 +499,11 @@ Ref<Resource> CanvasItemMaterialConversionPlugin::convert(const Ref<Resource> &p smat->set_shader(shader); List<PropertyInfo> params; - RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), ¶ms); + RS::get_singleton()->get_shader_parameter_list(mat->get_shader_rid(), ¶ms); for (const PropertyInfo &E : params) { Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name); - smat->set_shader_param(E.name, value); + smat->set_shader_parameter(E.name, value); } smat->set_render_priority(mat->get_render_priority()); @@ -465,11 +538,11 @@ Ref<Resource> ProceduralSkyMaterialConversionPlugin::convert(const Ref<Resource> smat->set_shader(shader); List<PropertyInfo> params; - RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), ¶ms); + RS::get_singleton()->get_shader_parameter_list(mat->get_shader_rid(), ¶ms); for (const PropertyInfo &E : params) { Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name); - smat->set_shader_param(E.name, value); + smat->set_shader_parameter(E.name, value); } smat->set_render_priority(mat->get_render_priority()); @@ -504,11 +577,11 @@ Ref<Resource> PanoramaSkyMaterialConversionPlugin::convert(const Ref<Resource> & smat->set_shader(shader); List<PropertyInfo> params; - RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), ¶ms); + RS::get_singleton()->get_shader_parameter_list(mat->get_shader_rid(), ¶ms); for (const PropertyInfo &E : params) { Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name); - smat->set_shader_param(E.name, value); + smat->set_shader_parameter(E.name, value); } smat->set_render_priority(mat->get_render_priority()); @@ -543,11 +616,11 @@ Ref<Resource> PhysicalSkyMaterialConversionPlugin::convert(const Ref<Resource> & smat->set_shader(shader); List<PropertyInfo> params; - RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), ¶ms); + RS::get_singleton()->get_shader_parameter_list(mat->get_shader_rid(), ¶ms); for (const PropertyInfo &E : params) { Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name); - smat->set_shader_param(E.name, value); + smat->set_shader_parameter(E.name, value); } smat->set_render_priority(mat->get_render_priority()); @@ -582,11 +655,11 @@ Ref<Resource> FogMaterialConversionPlugin::convert(const Ref<Resource> &p_resour smat->set_shader(shader); List<PropertyInfo> params; - RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), ¶ms); + RS::get_singleton()->get_shader_parameter_list(mat->get_shader_rid(), ¶ms); for (const PropertyInfo &E : params) { Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name); - smat->set_shader_param(E.name, value); + smat->set_shader_parameter(E.name, value); } smat->set_render_priority(mat->get_render_priority()); diff --git a/editor/plugins/material_editor_plugin.h b/editor/plugins/material_editor_plugin.h index 36c2df191d..63ee053b1d 100644 --- a/editor/plugins/material_editor_plugin.h +++ b/editor/plugins/material_editor_plugin.h @@ -1,62 +1,67 @@ -/*************************************************************************/ -/* material_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* material_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 MATERIAL_EDITOR_PLUGIN_H #define MATERIAL_EDITOR_PLUGIN_H -#include "editor/property_editor.h" -#include "scene/resources/primitive_meshes.h" - -#include "editor/editor_node.h" +#include "editor/editor_inspector.h" #include "editor/editor_plugin.h" +#include "editor/plugins/editor_resource_conversion_plugin.h" #include "scene/3d/camera_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/gui/color_rect.h" #include "scene/resources/material.h" +#include "scene/resources/primitive_meshes.h" +class SubViewport; class SubViewportContainer; +class TextureButton; class MaterialEditor : public Control { GDCLASS(MaterialEditor, Control); + Vector2 rot; + HBoxContainer *layout_2d = nullptr; ColorRect *rect_instance = nullptr; SubViewportContainer *vc = nullptr; SubViewport *viewport = nullptr; + Node3D *rotation = nullptr; MeshInstance3D *sphere_instance = nullptr; MeshInstance3D *box_instance = nullptr; DirectionalLight3D *light1 = nullptr; DirectionalLight3D *light2 = nullptr; Camera3D *camera = nullptr; + Ref<CameraAttributesPractical> camera_attributes; Ref<SphereMesh> sphere_mesh; Ref<BoxMesh> box_mesh; @@ -71,13 +76,25 @@ class MaterialEditor : public Control { Ref<Material> material; + struct ThemeCache { + Ref<Texture2D> light_1_on; + Ref<Texture2D> light_1_off; + Ref<Texture2D> light_2_on; + Ref<Texture2D> light_2_off; + Ref<Texture2D> sphere_on; + Ref<Texture2D> sphere_off; + Ref<Texture2D> box_on; + Ref<Texture2D> box_off; + Ref<Texture2D> checkerboard; + } theme_cache; + void _button_pressed(Node *p_button); - bool first_enter; protected: + virtual void _update_theme_item_cache() override; void _notification(int p_what); - - static void _bind_methods(); + void gui_input(const Ref<InputEvent> &p_event) override; + void _update_rotation(); public: void edit(Ref<Material> p_material, const Ref<Environment> &p_env); @@ -92,6 +109,8 @@ public: virtual bool can_handle(Object *p_object) override; virtual void parse_begin(Object *p_object) override; + void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); + EditorInspectorPluginMaterial(); }; @@ -101,7 +120,7 @@ class MaterialEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "Material"; } - MaterialEditorPlugin(EditorNode *p_node); + MaterialEditorPlugin(); }; class StandardMaterial3DConversionPlugin : public EditorResourceConversionPlugin { @@ -122,8 +141,8 @@ public: virtual Ref<Resource> convert(const Ref<Resource> &p_resource) const override; }; -class ParticlesMaterialConversionPlugin : public EditorResourceConversionPlugin { - GDCLASS(ParticlesMaterialConversionPlugin, EditorResourceConversionPlugin); +class ParticleProcessMaterialConversionPlugin : public EditorResourceConversionPlugin { + GDCLASS(ParticleProcessMaterialConversionPlugin, EditorResourceConversionPlugin); public: virtual String converts_to() const override; diff --git a/editor/plugins/mesh_editor_plugin.cpp b/editor/plugins/mesh_editor_plugin.cpp index daf68f247d..ee555ae8d4 100644 --- a/editor/plugins/mesh_editor_plugin.cpp +++ b/editor/plugins/mesh_editor_plugin.cpp @@ -1,42 +1,45 @@ -/*************************************************************************/ -/* mesh_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* mesh_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 "mesh_editor_plugin.h" +#include "core/config/project_settings.h" #include "editor/editor_scale.h" +#include "scene/gui/texture_button.h" +#include "scene/main/viewport.h" void MeshEditor::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { + if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { rot_x -= mm->get_relative().y * 0.01; rot_y -= mm->get_relative().x * 0.01; if (rot_x < -Math_PI / 2) { @@ -48,19 +51,23 @@ void MeshEditor::gui_input(const Ref<InputEvent> &p_event) { } } -void MeshEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - //get_scene()->connect("node_removed",this,"_node_removed"); +void MeshEditor::_update_theme_item_cache() { + SubViewportContainer::_update_theme_item_cache(); - if (first_enter) { - //it's in propertyeditor so. could be moved around + theme_cache.light_1_on = get_theme_icon(SNAME("MaterialPreviewLight1"), SNAME("EditorIcons")); + theme_cache.light_1_off = get_theme_icon(SNAME("MaterialPreviewLight1Off"), SNAME("EditorIcons")); + theme_cache.light_2_on = get_theme_icon(SNAME("MaterialPreviewLight2"), SNAME("EditorIcons")); + theme_cache.light_2_off = get_theme_icon(SNAME("MaterialPreviewLight2Off"), SNAME("EditorIcons")); +} - light_1_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight1"), SNAME("EditorIcons"))); - light_1_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight1Off"), SNAME("EditorIcons"))); - light_2_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight2"), SNAME("EditorIcons"))); - light_2_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight2Off"), SNAME("EditorIcons"))); - first_enter = false; - } +void MeshEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + light_1_switch->set_texture_normal(theme_cache.light_1_on); + light_1_switch->set_texture_pressed(theme_cache.light_1_off); + light_2_switch->set_texture_normal(theme_cache.light_2_on); + light_2_switch->set_texture_pressed(theme_cache.light_2_off); + } break; } } @@ -75,8 +82,8 @@ void MeshEditor::edit(Ref<Mesh> p_mesh) { mesh = p_mesh; mesh_instance->set_mesh(mesh); - rot_x = Math::deg2rad(-15.0); - rot_y = Math::deg2rad(30.0); + rot_x = Math::deg_to_rad(-15.0); + rot_y = Math::deg_to_rad(30.0); _update_rotation(); AABB aabb = mesh->get_aabb(); @@ -110,13 +117,18 @@ MeshEditor::MeshEditor() { viewport->set_world_3d(world_3d); //use own world add_child(viewport); viewport->set_disable_input(true); - viewport->set_msaa(Viewport::MSAA_2X); + viewport->set_msaa_3d(Viewport::MSAA_4X); set_stretch(true); camera = memnew(Camera3D); camera->set_transform(Transform3D(Basis(), Vector3(0, 0, 1.1))); camera->set_perspective(45, 0.1, 10); viewport->add_child(camera); + if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { + camera_attributes.instantiate(); + camera->set_attributes(camera_attributes); + } + light1 = memnew(DirectionalLight3D); light1->set_transform(Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0))); viewport->add_child(light1); @@ -135,7 +147,7 @@ MeshEditor::MeshEditor() { HBoxContainer *hb = memnew(HBoxContainer); add_child(hb); - hb->set_anchors_and_offsets_preset(Control::PRESET_WIDE, Control::PRESET_MODE_MINSIZE, 2); + hb->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 2); hb->add_spacer(); @@ -145,14 +157,12 @@ MeshEditor::MeshEditor() { light_1_switch = memnew(TextureButton); light_1_switch->set_toggle_mode(true); vb_light->add_child(light_1_switch); - light_1_switch->connect("pressed", callable_mp(this, &MeshEditor::_button_pressed), varray(light_1_switch)); + light_1_switch->connect("pressed", callable_mp(this, &MeshEditor::_button_pressed).bind(light_1_switch)); light_2_switch = memnew(TextureButton); light_2_switch->set_toggle_mode(true); vb_light->add_child(light_2_switch); - light_2_switch->connect("pressed", callable_mp(this, &MeshEditor::_button_pressed), varray(light_2_switch)); - - first_enter = true; + light_2_switch->connect("pressed", callable_mp(this, &MeshEditor::_button_pressed).bind(light_2_switch)); rot_x = 0; rot_y = 0; @@ -176,7 +186,7 @@ void EditorInspectorPluginMesh::parse_begin(Object *p_object) { add_custom_control(editor); } -MeshEditorPlugin::MeshEditorPlugin(EditorNode *p_node) { +MeshEditorPlugin::MeshEditorPlugin() { Ref<EditorInspectorPluginMesh> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/mesh_editor_plugin.h b/editor/plugins/mesh_editor_plugin.h index 613680e870..335244ffd2 100644 --- a/editor/plugins/mesh_editor_plugin.h +++ b/editor/plugins/mesh_editor_plugin.h @@ -1,68 +1,79 @@ -/*************************************************************************/ -/* mesh_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* mesh_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 MESH_EDITOR_PLUGIN_H #define MESH_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/editor_inspector.h" #include "editor/editor_plugin.h" #include "scene/3d/camera_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/gui/subviewport_container.h" +#include "scene/resources/camera_attributes.h" #include "scene/resources/material.h" +class SubViewport; +class TextureButton; + class MeshEditor : public SubViewportContainer { GDCLASS(MeshEditor, SubViewportContainer); float rot_x; float rot_y; - SubViewport *viewport; - MeshInstance3D *mesh_instance; - Node3D *rotation; - DirectionalLight3D *light1; - DirectionalLight3D *light2; - Camera3D *camera; + SubViewport *viewport = nullptr; + MeshInstance3D *mesh_instance = nullptr; + Node3D *rotation = nullptr; + DirectionalLight3D *light1 = nullptr; + DirectionalLight3D *light2 = nullptr; + Camera3D *camera = nullptr; + Ref<CameraAttributesPractical> camera_attributes; Ref<Mesh> mesh; - TextureButton *light_1_switch; - TextureButton *light_2_switch; + TextureButton *light_1_switch = nullptr; + TextureButton *light_2_switch = nullptr; - void _button_pressed(Node *p_button); - bool first_enter; + struct ThemeCache { + Ref<Texture2D> light_1_on; + Ref<Texture2D> light_1_off; + Ref<Texture2D> light_2_on; + Ref<Texture2D> light_2_off; + } theme_cache; + void _button_pressed(Node *p_button); void _update_rotation(); protected: + virtual void _update_theme_item_cache() override; void _notification(int p_what); void gui_input(const Ref<InputEvent> &p_event) override; @@ -85,7 +96,7 @@ class MeshEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "Mesh"; } - MeshEditorPlugin(EditorNode *p_node); + MeshEditorPlugin(); }; -#endif +#endif // MESH_EDITOR_PLUGIN_H diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp index 75e9cc23a1..e8976667dd 100644 --- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp +++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp @@ -1,41 +1,47 @@ -/*************************************************************************/ -/* mesh_instance_3d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* mesh_instance_3d_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 "mesh_instance_3d_editor_plugin.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_undo_redo_manager.h" #include "node_3d_editor_plugin.h" #include "scene/3d/collision_shape_3d.h" #include "scene/3d/navigation_region_3d.h" #include "scene/3d/physics_body_3d.h" #include "scene/gui/box_container.h" +#include "scene/gui/menu_button.h" +#include "scene/resources/concave_polygon_shape_3d.h" +#include "scene/resources/convex_polygon_shape_3d.h" +#include "scene/scene_string_names.h" void MeshInstance3DEditor::_node_removed(Node *p_node) { if (p_node == node) { @@ -59,12 +65,12 @@ void MeshInstance3DEditor::_menu_option(int p_option) { switch (p_option) { case MENU_OPTION_CREATE_STATIC_TRIMESH_BODY: { EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); List<Node *> selection = editor_selection->get_selected_node_list(); if (selection.is_empty()) { - Ref<Shape3D> shape = mesh->create_trimesh_shape(); + Ref<ConcavePolygonShape3D> shape = mesh->create_trimesh_shape(); if (shape.is_null()) { err_dialog->set_text(TTR("Couldn't create a Trimesh collision shape.")); err_dialog->popup_centered(); @@ -74,14 +80,16 @@ void MeshInstance3DEditor::_menu_option(int p_option) { CollisionShape3D *cshape = memnew(CollisionShape3D); cshape->set_shape(shape); StaticBody3D *body = memnew(StaticBody3D); - body->add_child(cshape); + body->add_child(cshape, true); - Node *owner = node == get_tree()->get_edited_scene_root() ? node : node->get_owner(); + Node *owner = get_tree()->get_edited_scene_root(); ur->create_action(TTR("Create Static Trimesh Body")); ur->add_do_method(node, "add_child", body, true); ur->add_do_method(body, "set_owner", owner); ur->add_do_method(cshape, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, body); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, cshape); ur->add_do_reference(body); ur->add_undo_method(node, "remove_child", body); ur->commit_action(); @@ -101,7 +109,7 @@ void MeshInstance3DEditor::_menu_option(int p_option) { continue; } - Ref<Shape3D> shape = m->create_trimesh_shape(); + Ref<ConcavePolygonShape3D> shape = m->create_trimesh_shape(); if (shape.is_null()) { continue; } @@ -109,13 +117,15 @@ void MeshInstance3DEditor::_menu_option(int p_option) { CollisionShape3D *cshape = memnew(CollisionShape3D); cshape->set_shape(shape); StaticBody3D *body = memnew(StaticBody3D); - body->add_child(cshape); + body->add_child(cshape, true); - Node *owner = instance == get_tree()->get_edited_scene_root() ? instance : instance->get_owner(); + Node *owner = get_tree()->get_edited_scene_root(); ur->add_do_method(instance, "add_child", body, true); ur->add_do_method(body, "set_owner", owner); ur->add_do_method(cshape, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, body); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, cshape); ur->add_do_reference(body); ur->add_undo_method(instance, "remove_child", body); } @@ -131,7 +141,7 @@ void MeshInstance3DEditor::_menu_option(int p_option) { return; } - Ref<Shape3D> shape = mesh->create_trimesh_shape(); + Ref<ConcavePolygonShape3D> shape = mesh->create_trimesh_shape(); if (shape.is_null()) { return; } @@ -140,15 +150,16 @@ void MeshInstance3DEditor::_menu_option(int p_option) { cshape->set_shape(shape); cshape->set_transform(node->get_transform()); - Node *owner = node->get_owner(); + Node *owner = get_tree()->get_edited_scene_root(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Create Trimesh Static Shape")); ur->add_do_method(node->get_parent(), "add_child", cshape, true); ur->add_do_method(node->get_parent(), "move_child", cshape, node->get_index() + 1); ur->add_do_method(cshape, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, cshape); ur->add_do_reference(cshape); ur->add_undo_method(node->get_parent(), "remove_child", cshape); ur->commit_action(); @@ -164,14 +175,14 @@ void MeshInstance3DEditor::_menu_option(int p_option) { bool simplify = (p_option == MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE); - Ref<Shape3D> shape = mesh->create_convex_shape(true, simplify); + Ref<ConvexPolygonShape3D> shape = mesh->create_convex_shape(true, simplify); if (shape.is_null()) { err_dialog->set_text(TTR("Couldn't create a single convex collision shape.")); err_dialog->popup_centered(); return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); if (simplify) { ur->create_action(TTR("Create Simplified Convex Shape")); @@ -183,11 +194,12 @@ void MeshInstance3DEditor::_menu_option(int p_option) { cshape->set_shape(shape); cshape->set_transform(node->get_transform()); - Node *owner = node->get_owner(); + Node *owner = get_tree()->get_edited_scene_root(); ur->add_do_method(node->get_parent(), "add_child", cshape, true); ur->add_do_method(node->get_parent(), "move_child", cshape, node->get_index() + 1); ur->add_do_method(cshape, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, cshape); ur->add_do_reference(cshape); ur->add_undo_method(node->get_parent(), "remove_child", cshape); @@ -210,20 +222,23 @@ void MeshInstance3DEditor::_menu_option(int p_option) { err_dialog->popup_centered(); return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Create Multiple Convex Shapes")); for (int i = 0; i < shapes.size(); i++) { CollisionShape3D *cshape = memnew(CollisionShape3D); + cshape->set_name("CollisionShape3D"); + cshape->set_shape(shapes[i]); cshape->set_transform(node->get_transform()); - Node *owner = node->get_owner(); + Node *owner = get_tree()->get_edited_scene_root(); ur->add_do_method(node->get_parent(), "add_child", cshape); ur->add_do_method(node->get_parent(), "move_child", cshape, node->get_index() + 1); ur->add_do_method(cshape, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, cshape); ur->add_do_reference(cshape); ur->add_undo_method(node->get_parent(), "remove_child", cshape); } @@ -242,13 +257,14 @@ void MeshInstance3DEditor::_menu_option(int p_option) { NavigationRegion3D *nmi = memnew(NavigationRegion3D); nmi->set_navigation_mesh(nmesh); - Node *owner = node == get_tree()->get_edited_scene_root() ? node : node->get_owner(); + Node *owner = get_tree()->get_edited_scene_root(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Create Navigation Mesh")); ur->add_do_method(node, "add_child", nmi, true); ur->add_do_method(nmi, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, nmi); ur->add_do_reference(nmi); ur->add_undo_method(node, "remove_child", nmi); @@ -258,6 +274,24 @@ void MeshInstance3DEditor::_menu_option(int p_option) { case MENU_OPTION_CREATE_OUTLINE_MESH: { outline_dialog->popup_centered(Vector2(200, 90)); } break; + case MENU_OPTION_CREATE_DEBUG_TANGENTS: { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Create Debug Tangents")); + + MeshInstance3D *tangents = node->create_debug_tangents_node(); + + if (tangents) { + Node *owner = get_tree()->get_edited_scene_root(); + + ur->add_do_reference(tangents); + ur->add_do_method(node, "add_child", tangents, true); + ur->add_do_method(tangents, "set_owner", owner); + + ur->add_undo_method(node, "remove_child", tangents); + } + + ur->commit_action(); + } break; case MENU_OPTION_CREATE_UV2: { Ref<ArrayMesh> mesh2 = node->get_mesh(); if (!mesh2.is_valid()) { @@ -266,13 +300,52 @@ void MeshInstance3DEditor::_menu_option(int p_option) { return; } - Error err = mesh2->lightmap_unwrap(node->get_global_transform()); + String path = mesh2->get_path(); + int srpos = path.find("::"); + if (srpos != -1) { + String base = path.substr(0, srpos); + if (ResourceLoader::get_resource_type(base) == "PackedScene") { + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + err_dialog->set_text(TTR("Mesh cannot unwrap UVs because it does not belong to the edited scene. Make it unique first.")); + err_dialog->popup_centered(); + return; + } + } else { + if (FileAccess::exists(path + ".import")) { + err_dialog->set_text(TTR("Mesh cannot unwrap UVs because it belongs to another resource which was imported from another file type. Make it unique first.")); + err_dialog->popup_centered(); + return; + } + } + } else { + if (FileAccess::exists(path + ".import")) { + err_dialog->set_text(TTR("Mesh cannot unwrap UVs because it was imported from another file type. Make it unique first.")); + err_dialog->popup_centered(); + return; + } + } + + Ref<ArrayMesh> unwrapped_mesh = mesh2->duplicate(false); + + Error err = unwrapped_mesh->lightmap_unwrap(node->get_global_transform()); if (err != OK) { err_dialog->set_text(TTR("UV Unwrap failed, mesh may not be manifold?")); err_dialog->popup_centered(); return; } + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Unwrap UV2")); + + ur->add_do_method(node, "set_mesh", unwrapped_mesh); + ur->add_do_reference(node); + ur->add_do_reference(mesh2.ptr()); + + ur->add_undo_method(node, "set_mesh", mesh2); + ur->add_undo_reference(unwrapped_mesh.ptr()); + + ur->commit_action(); + } break; case MENU_OPTION_DEBUG_UV1: { Ref<Mesh> mesh2 = node->get_mesh(); @@ -299,12 +372,13 @@ struct MeshInstance3DEditorEdgeSort { Vector2 a; Vector2 b; - bool operator<(const MeshInstance3DEditorEdgeSort &p_b) const { - if (a == p_b.a) { - return b < p_b.b; - } else { - return a < p_b.a; - } + static uint32_t hash(const MeshInstance3DEditorEdgeSort &p_edge) { + uint32_t h = hash_murmur3_one_32(HashMapHasherDefault::hash(p_edge.a)); + return hash_fmix32(hash_murmur3_one_32(HashMapHasherDefault::hash(p_edge.b), h)); + } + + bool operator==(const MeshInstance3DEditorEdgeSort &p_b) const { + return a == p_b.a && b == p_b.b; } MeshInstance3DEditorEdgeSort() {} @@ -323,7 +397,7 @@ void MeshInstance3DEditor::_create_uv_lines(int p_layer) { Ref<Mesh> mesh = node->get_mesh(); ERR_FAIL_COND(!mesh.is_valid()); - Set<MeshInstance3DEditorEdgeSort> edges; + HashSet<MeshInstance3DEditorEdgeSort, MeshInstance3DEditorEdgeSort> edges; uv_lines.clear(); for (int i = 0; i < mesh->get_surface_count(); i++) { if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { @@ -386,7 +460,7 @@ void MeshInstance3DEditor::_debug_uv_draw() { debug_uv->draw_rect(Rect2(Vector2(), debug_uv->get_size()), get_theme_color(SNAME("dark_color_3"), SNAME("Editor"))); debug_uv->draw_set_transform(Vector2(), 0, debug_uv->get_size()); // Use a translucent color to allow overlapping triangles to be visible. - debug_uv->draw_multiline(uv_lines, get_theme_color(SNAME("mono_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5), Math::round(EDSCALE)); + debug_uv->draw_multiline(uv_lines, get_theme_color(SNAME("mono_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5)); } void MeshInstance3DEditor::_create_outline_mesh() { @@ -417,17 +491,15 @@ void MeshInstance3DEditor::_create_outline_mesh() { MeshInstance3D *mi = memnew(MeshInstance3D); mi->set_mesh(mesho); - Node *owner = node->get_owner(); - if (get_tree()->get_edited_scene_root() == node) { - owner = node; - } + Node *owner = get_tree()->get_edited_scene_root(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Create Outline")); ur->add_do_method(node, "add_child", mi, true); ur->add_do_method(mi, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, mi); ur->add_do_reference(mi); ur->add_undo_method(node, "remove_child", mi); @@ -446,21 +518,22 @@ MeshInstance3DEditor::MeshInstance3DEditor() { options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MeshInstance3D"), SNAME("EditorIcons"))); options->get_popup()->add_item(TTR("Create Trimesh Static Body"), MENU_OPTION_CREATE_STATIC_TRIMESH_BODY); - options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a StaticBody3D and assigns a polygon-based collision shape to it automatically.\nThis is the most accurate (but slowest) option for collision detection.")); + options->get_popup()->set_item_tooltip(-1, TTR("Creates a StaticBody3D and assigns a polygon-based collision shape to it automatically.\nThis is the most accurate (but slowest) option for collision detection.")); options->get_popup()->add_separator(); options->get_popup()->add_item(TTR("Create Trimesh Collision Sibling"), MENU_OPTION_CREATE_TRIMESH_COLLISION_SHAPE); - options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is the most accurate (but slowest) option for collision detection.")); + options->get_popup()->set_item_tooltip(-1, TTR("Creates a polygon-based collision shape.\nThis is the most accurate (but slowest) option for collision detection.")); options->get_popup()->add_item(TTR("Create Single Convex Collision Sibling"), MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE); - options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a single convex collision shape.\nThis is the fastest (but least accurate) option for collision detection.")); + options->get_popup()->set_item_tooltip(-1, TTR("Creates a single convex collision shape.\nThis is the fastest (but least accurate) option for collision detection.")); options->get_popup()->add_item(TTR("Create Simplified Convex Collision Sibling"), MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE); - options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a simplified convex collision shape.\nThis is similar to single collision shape, but can result in a simpler geometry in some cases, at the cost of accuracy.")); + options->get_popup()->set_item_tooltip(-1, TTR("Creates a simplified convex collision shape.\nThis is similar to single collision shape, but can result in a simpler geometry in some cases, at the cost of accuracy.")); options->get_popup()->add_item(TTR("Create Multiple Convex Collision Siblings"), MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES); - options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is a performance middle-ground between a single convex collision and a polygon-based collision.")); + options->get_popup()->set_item_tooltip(-1, TTR("Creates a polygon-based collision shape.\nThis is a performance middle-ground between a single convex collision and a polygon-based collision.")); options->get_popup()->add_separator(); options->get_popup()->add_item(TTR("Create Navigation Mesh"), MENU_OPTION_CREATE_NAVMESH); options->get_popup()->add_separator(); options->get_popup()->add_item(TTR("Create Outline Mesh..."), MENU_OPTION_CREATE_OUTLINE_MESH); options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a static outline mesh. The outline mesh will have its normals flipped automatically.\nThis can be used instead of the StandardMaterial Grow property when using that property isn't possible.")); + options->get_popup()->add_item(TTR("Create Debug Tangents"), MENU_OPTION_CREATE_DEBUG_TANGENTS); options->get_popup()->add_separator(); options->get_popup()->add_item(TTR("View UV1"), MENU_OPTION_DEBUG_UV1); options->get_popup()->add_item(TTR("View UV2"), MENU_OPTION_DEBUG_UV2); @@ -470,7 +543,7 @@ MeshInstance3DEditor::MeshInstance3DEditor() { outline_dialog = memnew(ConfirmationDialog); outline_dialog->set_title(TTR("Create Outline Mesh")); - outline_dialog->get_ok_button()->set_text(TTR("Create")); + outline_dialog->set_ok_button_text(TTR("Create")); VBoxContainer *outline_dialog_vbc = memnew(VBoxContainer); outline_dialog->add_child(outline_dialog_vbc); @@ -515,10 +588,9 @@ void MeshInstance3DEditorPlugin::make_visible(bool p_visible) { } } -MeshInstance3DEditorPlugin::MeshInstance3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +MeshInstance3DEditorPlugin::MeshInstance3DEditorPlugin() { mesh_editor = memnew(MeshInstance3DEditor); - editor->get_main_control()->add_child(mesh_editor); + EditorNode::get_singleton()->get_main_screen_control()->add_child(mesh_editor); mesh_editor->options->hide(); } diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.h b/editor/plugins/mesh_instance_3d_editor_plugin.h index 1df72d107c..aa72d4fa33 100644 --- a/editor/plugins/mesh_instance_3d_editor_plugin.h +++ b/editor/plugins/mesh_instance_3d_editor_plugin.h @@ -1,41 +1,44 @@ -/*************************************************************************/ -/* mesh_instance_3d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 MESH_INSTANCE_EDITOR_PLUGIN_H -#define MESH_INSTANCE_EDITOR_PLUGIN_H - -#include "editor/editor_node.h" +/**************************************************************************/ +/* mesh_instance_3d_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 MESH_INSTANCE_3D_EDITOR_PLUGIN_H +#define MESH_INSTANCE_3D_EDITOR_PLUGIN_H + #include "editor/editor_plugin.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/gui/spin_box.h" +class AcceptDialog; +class ConfirmationDialog; +class MenuButton; + class MeshInstance3DEditor : public Control { GDCLASS(MeshInstance3DEditor, Control); @@ -47,22 +50,23 @@ class MeshInstance3DEditor : public Control { MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES, MENU_OPTION_CREATE_NAVMESH, MENU_OPTION_CREATE_OUTLINE_MESH, + MENU_OPTION_CREATE_DEBUG_TANGENTS, MENU_OPTION_CREATE_UV2, MENU_OPTION_DEBUG_UV1, MENU_OPTION_DEBUG_UV2, }; - MeshInstance3D *node; + MeshInstance3D *node = nullptr; - MenuButton *options; + MenuButton *options = nullptr; - ConfirmationDialog *outline_dialog; - SpinBox *outline_size; + ConfirmationDialog *outline_dialog = nullptr; + SpinBox *outline_size = nullptr; - AcceptDialog *err_dialog; + AcceptDialog *err_dialog = nullptr; - AcceptDialog *debug_uv_dialog; - Control *debug_uv; + AcceptDialog *debug_uv_dialog = nullptr; + Control *debug_uv = nullptr; Vector<Vector2> uv_lines; void _menu_option(int p_option); @@ -85,8 +89,7 @@ public: class MeshInstance3DEditorPlugin : public EditorPlugin { GDCLASS(MeshInstance3DEditorPlugin, EditorPlugin); - MeshInstance3DEditor *mesh_editor; - EditorNode *editor; + MeshInstance3DEditor *mesh_editor = nullptr; public: virtual String get_name() const override { return "MeshInstance3D"; } @@ -95,8 +98,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - MeshInstance3DEditorPlugin(EditorNode *p_node); + MeshInstance3DEditorPlugin(); ~MeshInstance3DEditorPlugin(); }; -#endif // MESH_EDITOR_PLUGIN_H +#endif // MESH_INSTANCE_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp index d82d0c6ffc..cf8555d07d 100644 --- a/editor/plugins/mesh_library_editor_plugin.cpp +++ b/editor/plugins/mesh_library_editor_plugin.cpp @@ -1,42 +1,45 @@ -/*************************************************************************/ -/* mesh_library_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* mesh_library_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 "mesh_library_editor_plugin.h" +#include "editor/editor_file_dialog.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/inspector_dock.h" #include "main/main.h" #include "node_3d_editor_plugin.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/navigation_region_3d.h" #include "scene/3d/physics_body_3d.h" +#include "scene/gui/menu_button.h" #include "scene/main/window.h" #include "scene/resources/packed_scene.h" @@ -70,7 +73,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, p_library->clear(); } - Map<int, MeshInstance3D *> mesh_instances; + HashMap<int, MeshInstance3D *> mesh_instances; for (int i = 0; i < p_scene->get_child_count(); i++) { Node *child = p_scene->get_child(i); @@ -136,9 +139,11 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, continue; } - //Transform3D shape_transform = sb->shape_owner_get_transform(E); - - //shape_transform.set_origin(shape_transform.get_origin() - phys_offset); + Transform3D shape_transform; + if (p_apply_xforms) { + shape_transform = mi->get_transform(); + } + shape_transform *= sb->get_transform() * sb->shape_owner_get_transform(E); for (int k = 0; k < sb->shape_owner_get_shape_count(E); k++) { Ref<Shape3D> collision = sb->shape_owner_get_shape(E, k); @@ -147,7 +152,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, } MeshLibrary::ShapeData shape_data; shape_data.shape = collision; - shape_data.local_transform = sb->get_transform() * sb->shape_owner_get_transform(E); + shape_data.local_transform = shape_transform; collisions.push_back(shape_data); } } @@ -155,23 +160,23 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, p_library->set_item_shapes(id, collisions); - Ref<NavigationMesh> navmesh; - Transform3D navmesh_transform; + Ref<NavigationMesh> navigation_mesh; + Transform3D navigation_mesh_transform; for (int j = 0; j < mi->get_child_count(); j++) { Node *child2 = mi->get_child(j); if (!Object::cast_to<NavigationRegion3D>(child2)) { continue; } NavigationRegion3D *sb = Object::cast_to<NavigationRegion3D>(child2); - navmesh = sb->get_navigation_mesh(); - navmesh_transform = sb->get_transform(); - if (!navmesh.is_null()) { + navigation_mesh = sb->get_navigation_mesh(); + navigation_mesh_transform = sb->get_transform(); + if (!navigation_mesh.is_null()) { break; } } - if (!navmesh.is_null()) { - p_library->set_item_navmesh(id, navmesh); - p_library->set_item_navmesh_transform(id, navmesh_transform); + if (!navigation_mesh.is_null()) { + p_library->set_item_navigation_mesh(id, navigation_mesh); + p_library->set_item_navigation_mesh_transform(id, navigation_mesh_transform); } } @@ -188,7 +193,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, } } - Vector<Ref<Texture2D>> textures = EditorInterface::get_singleton()->make_mesh_previews(meshes, &transforms, EditorSettings::get_singleton()->get("editors/grid_map/preview_size")); + Vector<Ref<Texture2D>> textures = EditorInterface::get_singleton()->make_mesh_previews(meshes, &transforms, EDITOR_GET("editors/grid_map/preview_size")); int j = 0; for (int i = 0; i < ids.size(); i++) { if (mesh_instances.find(ids[i])) { @@ -226,9 +231,9 @@ void MeshLibraryEditor::_menu_cbk(int p_option) { mesh_library->create_item(mesh_library->get_last_unused_item_id()); } break; case MENU_OPTION_REMOVE_ITEM: { - String p = editor->get_inspector()->get_selected_path(); - if (p.begins_with("/MeshLibrary/item") && p.get_slice_count("/") >= 3) { - to_erase = p.get_slice("/", 3).to_int(); + String p = InspectorDock::get_inspector_singleton()->get_selected_path(); + if (p.begins_with("item") && p.get_slice_count("/") >= 2) { + to_erase = p.get_slice("/", 1).to_int(); cd_remove->set_text(vformat(TTR("Remove item %d?"), to_erase)); cd_remove->popup_centered(Size2(300, 60)); } @@ -251,7 +256,7 @@ void MeshLibraryEditor::_menu_cbk(int p_option) { void MeshLibraryEditor::_bind_methods() { } -MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { +MeshLibraryEditor::MeshLibraryEditor() { file = memnew(EditorFileDialog); file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); //not for now? @@ -260,7 +265,7 @@ MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { file->clear_filters(); file->set_title(TTR("Import Scene")); for (int i = 0; i < extensions.size(); i++) { - file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); + file->add_filter("*." + extensions[i], extensions[i].to_upper()); } add_child(file); file->connect("file_selected", callable_mp(this, &MeshLibraryEditor::_import_scene_cbk)); @@ -268,7 +273,7 @@ MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { menu = memnew(MenuButton); Node3DEditor::get_singleton()->add_control_to_menu_panel(menu); menu->set_position(Point2(1, 1)); - menu->set_text(TTR("Mesh Library")); + menu->set_text(TTR("MeshLibrary")); menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MeshLibrary"), SNAME("EditorIcons"))); menu->get_popup()->add_item(TTR("Add Item"), MENU_OPTION_ADD_ITEM); menu->get_popup()->add_item(TTR("Remove Selected Item"), MENU_OPTION_REMOVE_ITEM); @@ -280,15 +285,14 @@ MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { menu->get_popup()->connect("id_pressed", callable_mp(this, &MeshLibraryEditor::_menu_cbk)); menu->hide(); - editor = p_editor; cd_remove = memnew(ConfirmationDialog); add_child(cd_remove); cd_remove->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_remove_confirm)); cd_update = memnew(ConfirmationDialog); add_child(cd_update); - cd_update->get_ok_button()->set_text("Apply without Transforms"); - cd_update->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(false)); - cd_update->add_button("Apply with Transforms")->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(true)); + cd_update->set_ok_button_text(TTR("Apply without Transforms")); + cd_update->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm).bind(false)); + cd_update->add_button(TTR("Apply with Transforms"))->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm).bind(true)); } void MeshLibraryEditorPlugin::edit(Object *p_node) { @@ -314,14 +318,11 @@ void MeshLibraryEditorPlugin::make_visible(bool p_visible) { } } -MeshLibraryEditorPlugin::MeshLibraryEditorPlugin(EditorNode *p_node) { - EDITOR_DEF("editors/grid_map/preview_size", 64); - mesh_library_editor = memnew(MeshLibraryEditor(p_node)); +MeshLibraryEditorPlugin::MeshLibraryEditorPlugin() { + mesh_library_editor = memnew(MeshLibraryEditor); - p_node->get_main_control()->add_child(mesh_library_editor); + EditorNode::get_singleton()->get_main_screen_control()->add_child(mesh_library_editor); mesh_library_editor->set_anchors_and_offsets_preset(Control::PRESET_TOP_WIDE); mesh_library_editor->set_end(Point2(0, 22)); mesh_library_editor->hide(); - - editor = nullptr; } diff --git a/editor/plugins/mesh_library_editor_plugin.h b/editor/plugins/mesh_library_editor_plugin.h index 7144f87ba6..c0900c8f35 100644 --- a/editor/plugins/mesh_library_editor_plugin.h +++ b/editor/plugins/mesh_library_editor_plugin.h @@ -1,51 +1,54 @@ -/*************************************************************************/ -/* mesh_library_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* mesh_library_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 MESH_LIBRARY_EDITOR_PLUGIN_H #define MESH_LIBRARY_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/editor_plugin.h" #include "scene/resources/mesh_library.h" +class EditorFileDialog; +class ConfirmationDialog; +class MenuButton; + class MeshLibraryEditor : public Control { GDCLASS(MeshLibraryEditor, Control); Ref<MeshLibrary> mesh_library; - EditorNode *editor; - MenuButton *menu; - ConfirmationDialog *cd_remove; - ConfirmationDialog *cd_update; - EditorFileDialog *file; - bool apply_xforms; - int to_erase; + MenuButton *menu = nullptr; + ConfirmationDialog *cd_remove = nullptr; + ConfirmationDialog *cd_update = nullptr; + EditorFileDialog *file = nullptr; + bool apply_xforms = false; + int to_erase = 0; enum { MENU_OPTION_ADD_ITEM, @@ -55,7 +58,7 @@ class MeshLibraryEditor : public Control { MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS }; - int option; + int option = 0; void _import_scene_cbk(const String &p_str); void _menu_cbk(int p_option); void _menu_remove_confirm(); @@ -72,14 +75,13 @@ public: void edit(const Ref<MeshLibrary> &p_mesh_library); static Error update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge = true, bool p_apply_xforms = false); - MeshLibraryEditor(EditorNode *p_editor); + MeshLibraryEditor(); }; class MeshLibraryEditorPlugin : public EditorPlugin { GDCLASS(MeshLibraryEditorPlugin, EditorPlugin); - MeshLibraryEditor *mesh_library_editor; - EditorNode *editor; + MeshLibraryEditor *mesh_library_editor = nullptr; public: virtual String get_name() const override { return "MeshLibrary"; } @@ -88,7 +90,7 @@ public: virtual bool handles(Object *p_node) const override; virtual void make_visible(bool p_visible) override; - MeshLibraryEditorPlugin(EditorNode *p_node); + MeshLibraryEditorPlugin(); }; #endif // MESH_LIBRARY_EDITOR_PLUGIN_H diff --git a/editor/plugins/multimesh_editor_plugin.cpp b/editor/plugins/multimesh_editor_plugin.cpp index 4ec65ea257..9845e8a9c3 100644 --- a/editor/plugins/multimesh_editor_plugin.cpp +++ b/editor/plugins/multimesh_editor_plugin.cpp @@ -1,38 +1,42 @@ -/*************************************************************************/ -/* multimesh_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* multimesh_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 "multimesh_editor_plugin.h" +#include "editor/editor_node.h" +#include "editor/scene_tree_editor.h" #include "node_3d_editor_plugin.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/gui/box_container.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/option_button.h" void MultiMeshEditor::_node_removed(Node *p_node) { if (p_node == node) { @@ -103,9 +107,9 @@ void MultiMeshEditor::_populate() { return; } - GeometryInstance3D *ss_instance = Object::cast_to<MeshInstance3D>(ss_node); + MeshInstance3D *ss_instance = Object::cast_to<MeshInstance3D>(ss_node); - if (!ss_instance) { + if (!ss_instance || !ss_instance->get_mesh().is_valid()) { err_dialog->set_text(TTR("Surface source is invalid (no geometry).")); err_dialog->popup_centered(); return; @@ -113,7 +117,7 @@ void MultiMeshEditor::_populate() { Transform3D geom_xform = node->get_global_transform().affine_inverse() * ss_instance->get_global_transform(); - Vector<Face3> geometry = ss_instance->get_faces(VisualInstance3D::FACES_SOLID); + Vector<Face3> geometry = ss_instance->get_mesh()->get_faces(); if (geometry.size() == 0) { err_dialog->set_text(TTR("Surface source is invalid (no faces).")); @@ -139,7 +143,7 @@ void MultiMeshEditor::_populate() { const Face3 *r = faces.ptr(); float area_accum = 0; - Map<float, int> triangle_area_map; + RBMap<float, int> triangle_area_map; for (int i = 0; i < facecount; i++) { float area = r[i].get_area(); if (area < CMP_EPSILON) { @@ -178,9 +182,9 @@ void MultiMeshEditor::_populate() { for (int i = 0; i < instance_count; i++) { float areapos = Math::random(0.0f, area_accum); - Map<float, int>::Element *E = triangle_area_map.find_closest(areapos); + RBMap<float, int>::Iterator E = triangle_area_map.find_closest(areapos); ERR_FAIL_COND(!E); - int index = E->get(); + int index = E->value; ERR_FAIL_INDEX(index, facecount); // ok FINALLY get face @@ -198,9 +202,9 @@ void MultiMeshEditor::_populate() { Basis post_xform; - post_xform.rotate(xform.basis.get_axis(1), -Math::random(-_rotate_random, _rotate_random) * Math_PI); - post_xform.rotate(xform.basis.get_axis(2), -Math::random(-_tilt_random, _tilt_random) * Math_PI); - post_xform.rotate(xform.basis.get_axis(0), -Math::random(-_tilt_random, _tilt_random) * Math_PI); + post_xform.rotate(xform.basis.get_column(1), -Math::random(-_rotate_random, _rotate_random) * Math_PI); + post_xform.rotate(xform.basis.get_column(2), -Math::random(-_tilt_random, _tilt_random) * Math_PI); + post_xform.rotate(xform.basis.get_column(0), -Math::random(-_tilt_random, _tilt_random) * Math_PI); xform.basis = post_xform * xform.basis; //xform.basis.orthonormalize(); @@ -289,7 +293,7 @@ MultiMeshEditor::MultiMeshEditor() { Button *b = memnew(Button); hbc->add_child(b); b->set_text(".."); - b->connect("pressed", callable_mp(this, &MultiMeshEditor::_browse), make_binds(false)); + b->connect("pressed", callable_mp(this, &MultiMeshEditor::_browse).bind(false)); vbc->add_margin_child(TTR("Target Surface:"), hbc); @@ -301,7 +305,7 @@ MultiMeshEditor::MultiMeshEditor() { hbc->add_child(b); b->set_text(".."); vbc->add_margin_child(TTR("Source Mesh:"), hbc); - b->connect("pressed", callable_mp(this, &MultiMeshEditor::_browse), make_binds(true)); + b->connect("pressed", callable_mp(this, &MultiMeshEditor::_browse).bind(true)); populate_axis = memnew(OptionButton); populate_axis->add_item(TTR("X-Axis")); @@ -345,7 +349,7 @@ MultiMeshEditor::MultiMeshEditor() { populate_amount->set_value(128); vbc->add_margin_child(TTR("Amount:"), populate_amount); - populate_dialog->get_ok_button()->set_text(TTR("Populate")); + populate_dialog->set_ok_button_text(TTR("Populate")); populate_dialog->get_ok_button()->connect("pressed", callable_mp(this, &MultiMeshEditor::_populate)); std = memnew(SceneTreeDialog); @@ -375,10 +379,9 @@ void MultiMeshEditorPlugin::make_visible(bool p_visible) { } } -MultiMeshEditorPlugin::MultiMeshEditorPlugin(EditorNode *p_node) { - editor = p_node; +MultiMeshEditorPlugin::MultiMeshEditorPlugin() { multimesh_editor = memnew(MultiMeshEditor); - editor->get_main_control()->add_child(multimesh_editor); + EditorNode::get_singleton()->get_main_screen_control()->add_child(multimesh_editor); multimesh_editor->options->hide(); } diff --git a/editor/plugins/multimesh_editor_plugin.h b/editor/plugins/multimesh_editor_plugin.h index ae18edd90a..b21a932809 100644 --- a/editor/plugins/multimesh_editor_plugin.h +++ b/editor/plugins/multimesh_editor_plugin.h @@ -1,66 +1,72 @@ -/*************************************************************************/ -/* multimesh_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* multimesh_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 MULTIMESH_EDITOR_PLUGIN_H #define MULTIMESH_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/multimesh_instance_3d.h" +#include "scene/gui/slider.h" #include "scene/gui/spin_box.h" +class AcceptDialog; +class ConfirmationDialog; +class MenuButton; +class OptionButton; +class SceneTreeDialog; + class MultiMeshEditor : public Control { GDCLASS(MultiMeshEditor, Control); friend class MultiMeshEditorPlugin; - AcceptDialog *err_dialog; - MenuButton *options; - MultiMeshInstance3D *_last_pp_node; - bool browsing_source; + AcceptDialog *err_dialog = nullptr; + MenuButton *options = nullptr; + MultiMeshInstance3D *_last_pp_node = nullptr; + bool browsing_source = false; - Panel *panel; - MultiMeshInstance3D *node; + Panel *panel = nullptr; + MultiMeshInstance3D *node = nullptr; - LineEdit *surface_source; - LineEdit *mesh_source; + LineEdit *surface_source = nullptr; + LineEdit *mesh_source = nullptr; - SceneTreeDialog *std; + SceneTreeDialog *std = nullptr; - ConfirmationDialog *populate_dialog; - OptionButton *populate_axis; - HSlider *populate_rotate_random; - HSlider *populate_tilt_random; - SpinBox *populate_scale_random; - SpinBox *populate_scale; - SpinBox *populate_amount; + ConfirmationDialog *populate_dialog = nullptr; + OptionButton *populate_axis = nullptr; + HSlider *populate_rotate_random = nullptr; + HSlider *populate_tilt_random = nullptr; + SpinBox *populate_scale_random = nullptr; + SpinBox *populate_scale = nullptr; + SpinBox *populate_amount = nullptr; enum Menu { MENU_OPTION_POPULATE @@ -83,8 +89,7 @@ public: class MultiMeshEditorPlugin : public EditorPlugin { GDCLASS(MultiMeshEditorPlugin, EditorPlugin); - MultiMeshEditor *multimesh_editor; - EditorNode *editor; + MultiMeshEditor *multimesh_editor = nullptr; public: virtual String get_name() const override { return "MultiMesh"; } @@ -93,7 +98,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - MultiMeshEditorPlugin(EditorNode *p_node); + MultiMeshEditorPlugin(); ~MultiMeshEditorPlugin(); }; diff --git a/editor/plugins/navigation_link_2d_editor_plugin.cpp b/editor/plugins/navigation_link_2d_editor_plugin.cpp new file mode 100644 index 0000000000..dff92ced27 --- /dev/null +++ b/editor/plugins/navigation_link_2d_editor_plugin.cpp @@ -0,0 +1,188 @@ +/**************************************************************************/ +/* navigation_link_2d_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 "navigation_link_2d_editor_plugin.h" + +#include "canvas_item_editor_plugin.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "servers/navigation_server_3d.h" + +void NavigationLink2DEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", callable_mp(this, &NavigationLink2DEditor::_node_removed)); + } break; + + case NOTIFICATION_EXIT_TREE: { + get_tree()->disconnect("node_removed", callable_mp(this, &NavigationLink2DEditor::_node_removed)); + } break; + } +} + +void NavigationLink2DEditor::_node_removed(Node *p_node) { + if (p_node == node) { + node = nullptr; + } +} + +bool NavigationLink2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { + if (!node || !node->is_visible_in_tree()) { + return false; + } + + real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); + Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { + if (mb->is_pressed()) { + // Start position + if (xform.xform(node->get_start_position()).distance_to(mb->get_position()) < grab_threshold) { + start_grabbed = true; + original_start_position = node->get_start_position(); + + return true; + } else { + start_grabbed = false; + } + + // End position + if (xform.xform(node->get_end_position()).distance_to(mb->get_position()) < grab_threshold) { + end_grabbed = true; + original_end_position = node->get_end_position(); + + return true; + } else { + end_grabbed = false; + } + } else { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + if (start_grabbed) { + undo_redo->create_action(TTR("Set start_position")); + undo_redo->add_do_method(node, "set_start_position", node->get_start_position()); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_method(node, "set_start_position", original_start_position); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + undo_redo->commit_action(); + + start_grabbed = false; + + return true; + } + + if (end_grabbed) { + undo_redo->create_action(TTR("Set end_position")); + undo_redo->add_do_method(node, "set_end_position", node->get_end_position()); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_method(node, "set_end_position", original_end_position); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + undo_redo->commit_action(); + + end_grabbed = false; + + return true; + } + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + Vector2 point = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mm->get_position())); + point = node->get_global_transform().affine_inverse().xform(point); + + if (start_grabbed) { + node->set_start_position(point); + canvas_item_editor->update_viewport(); + + return true; + } + + if (end_grabbed) { + node->set_end_position(point); + canvas_item_editor->update_viewport(); + + return true; + } + } + + return false; +} + +void NavigationLink2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { + if (!node || !node->is_visible_in_tree()) { + return; + } + + Transform2D gt = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + Vector2 global_start_position = gt.xform(node->get_start_position()); + Vector2 global_end_position = gt.xform(node->get_end_position()); + + // Only drawing the handles here, since the debug rendering will fill in the rest. + const Ref<Texture2D> handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); + p_overlay->draw_texture(handle, global_start_position - handle->get_size() / 2); + p_overlay->draw_texture(handle, global_end_position - handle->get_size() / 2); +} + +void NavigationLink2DEditor::edit(NavigationLink2D *p_node) { + if (!canvas_item_editor) { + canvas_item_editor = CanvasItemEditor::get_singleton(); + } + + if (p_node) { + node = p_node; + } else { + node = nullptr; + } + + canvas_item_editor->update_viewport(); +} + +/////////////////////// + +void NavigationLink2DEditorPlugin::edit(Object *p_object) { + editor->edit(Object::cast_to<NavigationLink2D>(p_object)); +} + +bool NavigationLink2DEditorPlugin::handles(Object *p_object) const { + return Object::cast_to<NavigationLink2D>(p_object) != nullptr; +} + +void NavigationLink2DEditorPlugin::make_visible(bool p_visible) { + if (!p_visible) { + edit(nullptr); + } +} + +NavigationLink2DEditorPlugin::NavigationLink2DEditorPlugin() { + editor = memnew(NavigationLink2DEditor); + EditorNode::get_singleton()->get_gui_base()->add_child(editor); +} diff --git a/editor/plugins/navigation_link_2d_editor_plugin.h b/editor/plugins/navigation_link_2d_editor_plugin.h new file mode 100644 index 0000000000..ea731ca2cd --- /dev/null +++ b/editor/plugins/navigation_link_2d_editor_plugin.h @@ -0,0 +1,79 @@ +/**************************************************************************/ +/* navigation_link_2d_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 NAVIGATION_LINK_2D_EDITOR_PLUGIN_H +#define NAVIGATION_LINK_2D_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "scene/2d/navigation_link_2d.h" + +class CanvasItemEditor; + +class NavigationLink2DEditor : public Control { + GDCLASS(NavigationLink2DEditor, Control); + + CanvasItemEditor *canvas_item_editor = nullptr; + NavigationLink2D *node = nullptr; + + bool start_grabbed = false; + Vector2 original_start_position; + + bool end_grabbed = false; + Vector2 original_end_position; + +protected: + void _notification(int p_what); + void _node_removed(Node *p_node); + +public: + bool forward_canvas_gui_input(const Ref<InputEvent> &p_event); + void forward_canvas_draw_over_viewport(Control *p_overlay); + void edit(NavigationLink2D *p_node); +}; + +class NavigationLink2DEditorPlugin : public EditorPlugin { + GDCLASS(NavigationLink2DEditorPlugin, EditorPlugin); + + NavigationLink2DEditor *editor = nullptr; + +public: + virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return editor->forward_canvas_gui_input(p_event); } + virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { editor->forward_canvas_draw_over_viewport(p_overlay); } + + virtual String get_name() const override { return "NavigationLink2D"; } + bool has_main_screen() const override { return false; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + NavigationLink2DEditorPlugin(); +}; + +#endif // NAVIGATION_LINK_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/navigation_polygon_editor_plugin.cpp b/editor/plugins/navigation_polygon_editor_plugin.cpp index e9e2a843cd..957a520d8a 100644 --- a/editor/plugins/navigation_polygon_editor_plugin.cpp +++ b/editor/plugins/navigation_polygon_editor_plugin.cpp @@ -1,35 +1,38 @@ -/*************************************************************************/ -/* navigation_polygon_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* navigation_polygon_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 "navigation_polygon_editor_plugin.h" +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" + Ref<NavigationPolygon> NavigationPolygonEditor::_ensure_navpoly() const { Ref<NavigationPolygon> navpoly = node->get_navigation_polygon(); if (!navpoly.is_valid()) { @@ -73,6 +76,7 @@ void NavigationPolygonEditor::_set_polygon(int p_idx, const Variant &p_polygon) void NavigationPolygonEditor::_action_add_polygon(const Variant &p_polygon) { Ref<NavigationPolygon> navpoly = _ensure_navpoly(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->add_do_method(navpoly.ptr(), "add_outline", p_polygon); undo_redo->add_undo_method(navpoly.ptr(), "remove_outline", navpoly->get_outline_count()); undo_redo->add_do_method(navpoly.ptr(), "make_polygons_from_outlines"); @@ -81,6 +85,7 @@ void NavigationPolygonEditor::_action_add_polygon(const Variant &p_polygon) { void NavigationPolygonEditor::_action_remove_polygon(int p_idx) { Ref<NavigationPolygon> navpoly = _ensure_navpoly(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->add_do_method(navpoly.ptr(), "remove_outline", p_idx); undo_redo->add_undo_method(navpoly.ptr(), "add_outline_at_index", navpoly->get_outline(p_idx), p_idx); undo_redo->add_do_method(navpoly.ptr(), "make_polygons_from_outlines"); @@ -89,6 +94,7 @@ void NavigationPolygonEditor::_action_remove_polygon(int p_idx) { void NavigationPolygonEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) { Ref<NavigationPolygon> navpoly = _ensure_navpoly(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->add_do_method(navpoly.ptr(), "set_outline", p_idx, p_polygon); undo_redo->add_undo_method(navpoly.ptr(), "set_outline", p_idx, p_previous); undo_redo->add_do_method(navpoly.ptr(), "make_polygons_from_outlines"); @@ -104,19 +110,17 @@ void NavigationPolygonEditor::_create_resource() { return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Create Navigation Polygon")); undo_redo->add_do_method(node, "set_navigation_polygon", Ref<NavigationPolygon>(memnew(NavigationPolygon))); - undo_redo->add_undo_method(node, "set_navigation_polygon", Variant(REF())); + undo_redo->add_undo_method(node, "set_navigation_polygon", Variant(Ref<RefCounted>())); undo_redo->commit_action(); _menu_option(MODE_CREATE); } -NavigationPolygonEditor::NavigationPolygonEditor(EditorNode *p_editor) : - AbstractPolygon2DEditor(p_editor) { - node = nullptr; -} +NavigationPolygonEditor::NavigationPolygonEditor() {} -NavigationPolygonEditorPlugin::NavigationPolygonEditorPlugin(EditorNode *p_node) : - AbstractPolygon2DEditorPlugin(p_node, memnew(NavigationPolygonEditor(p_node)), "NavigationRegion2D") { +NavigationPolygonEditorPlugin::NavigationPolygonEditorPlugin() : + AbstractPolygon2DEditorPlugin(memnew(NavigationPolygonEditor), "NavigationRegion2D") { } diff --git a/editor/plugins/navigation_polygon_editor_plugin.h b/editor/plugins/navigation_polygon_editor_plugin.h index 446083902c..f43c052dd3 100644 --- a/editor/plugins/navigation_polygon_editor_plugin.h +++ b/editor/plugins/navigation_polygon_editor_plugin.h @@ -1,35 +1,35 @@ -/*************************************************************************/ -/* navigation_polygon_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* navigation_polygon_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 NAVIGATIONPOLYGONEDITORPLUGIN_H -#define NAVIGATIONPOLYGONEDITORPLUGIN_H +#ifndef NAVIGATION_POLYGON_EDITOR_PLUGIN_H +#define NAVIGATION_POLYGON_EDITOR_PLUGIN_H #include "editor/plugins/abstract_polygon_2d_editor.h" #include "scene/2d/navigation_region_2d.h" @@ -37,7 +37,7 @@ class NavigationPolygonEditor : public AbstractPolygon2DEditor { GDCLASS(NavigationPolygonEditor, AbstractPolygon2DEditor); - NavigationRegion2D *node; + NavigationRegion2D *node = nullptr; Ref<NavigationPolygon> _ensure_navpoly() const; @@ -57,14 +57,14 @@ protected: virtual void _create_resource() override; public: - NavigationPolygonEditor(EditorNode *p_editor); + NavigationPolygonEditor(); }; class NavigationPolygonEditorPlugin : public AbstractPolygon2DEditorPlugin { GDCLASS(NavigationPolygonEditorPlugin, AbstractPolygon2DEditorPlugin); public: - NavigationPolygonEditorPlugin(EditorNode *p_node); + NavigationPolygonEditorPlugin(); }; -#endif // NAVIGATIONPOLYGONEDITORPLUGIN_H +#endif // NAVIGATION_POLYGON_EDITOR_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 474e84cae8..bcb94b0f32 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -1,38 +1,42 @@ -/*************************************************************************/ -/* node_3d_editor_gizmos.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* node_3d_editor_gizmos.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 "node_3d_editor_gizmos.h" +#include "core/config/project_settings.h" #include "core/math/convex_hull.h" #include "core/math/geometry_2d.h" #include "core/math/geometry_3d.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/audio_listener_3d.h" #include "scene/3d/audio_stream_player_3d.h" @@ -45,16 +49,19 @@ #include "scene/3d/gpu_particles_3d.h" #include "scene/3d/gpu_particles_collision_3d.h" #include "scene/3d/joint_3d.h" +#include "scene/3d/label_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/lightmap_gi.h" #include "scene/3d/lightmap_probe.h" +#include "scene/3d/marker_3d.h" #include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/navigation_link_3d.h" #include "scene/3d/navigation_region_3d.h" #include "scene/3d/occluder_instance_3d.h" -#include "scene/3d/position_3d.h" #include "scene/3d/ray_cast_3d.h" #include "scene/3d/reflection_probe.h" -#include "scene/3d/soft_dynamic_body_3d.h" +#include "scene/3d/shape_cast_3d.h" +#include "scene/3d/soft_body_3d.h" #include "scene/3d/spring_arm_3d.h" #include "scene/3d/sprite_3d.h" #include "scene/3d/vehicle_body_3d.h" @@ -71,6 +78,7 @@ #include "scene/resources/sphere_shape_3d.h" #include "scene/resources/surface_tool.h" #include "scene/resources/world_boundary_shape_3d.h" +#include "servers/navigation_server_3d.h" #define HANDLE_HALF_SIZE 9.5 @@ -92,6 +100,7 @@ bool EditorNode3DGizmo::is_editable() const { } void EditorNode3DGizmo::clear() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); for (int i = 0; i < instances.size(); i++) { if (instances[i].instance.is_valid()) { RS::get_singleton()->free(instances[i].instance); @@ -103,7 +112,9 @@ void EditorNode3DGizmo::clear() { collision_mesh = Ref<TriangleMesh>(); instances.clear(); handles.clear(); + handle_ids.clear(); secondary_handles.clear(); + secondary_handle_ids.clear(); } void EditorNode3DGizmo::redraw() { @@ -117,52 +128,52 @@ void EditorNode3DGizmo::redraw() { } } -String EditorNode3DGizmo::get_handle_name(int p_id) const { +String EditorNode3DGizmo::get_handle_name(int p_id, bool p_secondary) const { String ret; - if (GDVIRTUAL_CALL(_get_handle_name, p_id, ret)) { + if (GDVIRTUAL_CALL(_get_handle_name, p_id, p_secondary, ret)) { return ret; } ERR_FAIL_COND_V(!gizmo_plugin, ""); - return gizmo_plugin->get_handle_name(this, p_id); + return gizmo_plugin->get_handle_name(this, p_id, p_secondary); } -bool EditorNode3DGizmo::is_handle_highlighted(int p_id) const { +bool EditorNode3DGizmo::is_handle_highlighted(int p_id, bool p_secondary) const { bool success; - if (GDVIRTUAL_CALL(_is_handle_highlighted, p_id, success)) { + if (GDVIRTUAL_CALL(_is_handle_highlighted, p_id, p_secondary, success)) { return success; } ERR_FAIL_COND_V(!gizmo_plugin, false); - return gizmo_plugin->is_handle_highlighted(this, p_id); + return gizmo_plugin->is_handle_highlighted(this, p_id, p_secondary); } -Variant EditorNode3DGizmo::get_handle_value(int p_id) const { +Variant EditorNode3DGizmo::get_handle_value(int p_id, bool p_secondary) const { Variant value; - if (GDVIRTUAL_CALL(_get_handle_value, p_id, value)) { + if (GDVIRTUAL_CALL(_get_handle_value, p_id, p_secondary, value)) { return value; } ERR_FAIL_COND_V(!gizmo_plugin, Variant()); - return gizmo_plugin->get_handle_value(this, p_id); + return gizmo_plugin->get_handle_value(this, p_id, p_secondary); } -void EditorNode3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) { - if (GDVIRTUAL_CALL(_set_handle, p_id, p_camera, p_point)) { +void EditorNode3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + if (GDVIRTUAL_CALL(_set_handle, p_id, p_secondary, p_camera, p_point)) { return; } ERR_FAIL_COND(!gizmo_plugin); - gizmo_plugin->set_handle(this, p_id, p_camera, p_point); + gizmo_plugin->set_handle(this, p_id, p_secondary, p_camera, p_point); } -void EditorNode3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cancel) { - if (GDVIRTUAL_CALL(_commit_handle, p_id, p_restore, p_cancel)) { +void EditorNode3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + if (GDVIRTUAL_CALL(_commit_handle, p_id, p_secondary, p_restore, p_cancel)) { return; } ERR_FAIL_COND(!gizmo_plugin); - gizmo_plugin->commit_handle(this, p_id, p_restore, p_cancel); + gizmo_plugin->commit_handle(this, p_id, p_secondary, p_restore, p_cancel); } int EditorNode3DGizmo::subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 &p_point) const { @@ -224,7 +235,7 @@ void EditorNode3DGizmo::commit_subgizmos(const Vector<int> &p_ids, const Vector< gizmo_plugin->commit_subgizmos(this, p_ids, p_restore, p_cancel); } -void EditorNode3DGizmo::set_spatial_node(Node3D *p_node) { +void EditorNode3DGizmo::set_node_3d(Node3D *p_node) { ERR_FAIL_NULL(p_node); spatial_node = p_node; } @@ -242,6 +253,7 @@ void EditorNode3DGizmo::Instance::create_instance(Node3D *p_base, bool p_hidden) int layer = p_hidden ? 0 : 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER; RS::get_singleton()->instance_set_layer_mask(instance, layer); //gizmos are 26 RS::get_singleton()->instance_geometry_set_flag(instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); } void EditorNode3DGizmo::add_mesh(const Ref<Mesh> &p_mesh, const Ref<Material> &p_material, const Transform3D &p_xform, const Ref<SkinReference> &p_skin_reference) { @@ -324,37 +336,34 @@ void EditorNode3DGizmo::add_unscaled_billboard(const Ref<Material> &p_material, ERR_FAIL_COND(!spatial_node); Instance ins; - Vector<Vector3> vs; - Vector<Vector2> uv; - Vector<Color> colors; - - vs.push_back(Vector3(-p_scale, p_scale, 0)); - vs.push_back(Vector3(p_scale, p_scale, 0)); - vs.push_back(Vector3(p_scale, -p_scale, 0)); - vs.push_back(Vector3(-p_scale, -p_scale, 0)); - - uv.push_back(Vector2(0, 0)); - uv.push_back(Vector2(1, 0)); - uv.push_back(Vector2(1, 1)); - uv.push_back(Vector2(0, 1)); - - colors.push_back(p_modulate); - colors.push_back(p_modulate); - colors.push_back(p_modulate); - colors.push_back(p_modulate); + Vector<Vector3> vs = { + Vector3(-p_scale, p_scale, 0), + Vector3(p_scale, p_scale, 0), + Vector3(p_scale, -p_scale, 0), + Vector3(-p_scale, -p_scale, 0) + }; + + Vector<Vector2> uv = { + Vector2(0, 0), + Vector2(1, 0), + Vector2(1, 1), + Vector2(0, 1) + }; + + Vector<Color> colors = { + p_modulate, + p_modulate, + p_modulate, + p_modulate + }; + + Vector<int> indices = { 0, 1, 2, 0, 2, 3 }; Ref<ArrayMesh> mesh = memnew(ArrayMesh); Array a; a.resize(Mesh::ARRAY_MAX); a[Mesh::ARRAY_VERTEX] = vs; a[Mesh::ARRAY_TEX_UV] = uv; - Vector<int> indices; - indices.push_back(0); - indices.push_back(1); - indices.push_back(2); - indices.push_back(0); - indices.push_back(2); - indices.push_back(3); a[Mesh::ARRAY_INDEX] = indices; a[Mesh::ARRAY_COLOR] = colors; mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a); @@ -401,16 +410,20 @@ void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref< return; } - ERR_FAIL_COND(!spatial_node); + ERR_FAIL_NULL(spatial_node); + + Vector<Vector3> &handle_list = p_secondary ? secondary_handles : handles; + Vector<int> &id_list = p_secondary ? secondary_handle_ids : handle_ids; if (p_ids.is_empty()) { - ERR_FAIL_COND_MSG((!handles.is_empty() && !handle_ids.is_empty()) || (!secondary_handles.is_empty() && !secondary_handle_ids.is_empty()), "Fail"); + ERR_FAIL_COND_MSG(!id_list.is_empty(), "IDs must be provided for all handles, as handles with IDs already exist."); } else { - ERR_FAIL_COND_MSG(handles.size() != handle_ids.size() || secondary_handles.size() != secondary_handle_ids.size(), "Fail"); + ERR_FAIL_COND_MSG(p_handles.size() != p_ids.size(), "The number of IDs should be the same as the number of handles."); } bool is_current_hover_gizmo = Node3DEditor::get_singleton()->get_current_hover_gizmo() == this; - int current_hover_handle = Node3DEditor::get_singleton()->get_current_hover_gizmo_handle(); + bool current_hover_handle_secondary; + int current_hover_handle = Node3DEditor::get_singleton()->get_current_hover_gizmo_handle(current_hover_handle_secondary); Instance ins; Ref<ArrayMesh> mesh = memnew(ArrayMesh); @@ -424,12 +437,12 @@ void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref< Color *w = colors.ptrw(); for (int i = 0; i < p_handles.size(); i++) { Color col(1, 1, 1, 1); - if (is_handle_highlighted(i)) { + if (is_handle_highlighted(i, p_secondary)) { col = Color(0, 0, 1, 0.9); } int id = p_ids.is_empty() ? i : p_ids[i]; - if (!is_current_hover_gizmo || current_hover_handle != id) { + if (!is_current_hover_gizmo || current_hover_handle != id || p_secondary != current_hover_handle_secondary) { col.a = 0.8; } @@ -458,24 +471,22 @@ void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref< } instances.push_back(ins); - Vector<Vector3> &h = p_secondary ? secondary_handles : handles; - int current_size = h.size(); - h.resize(current_size + p_handles.size()); + int current_size = handle_list.size(); + handle_list.resize(current_size + p_handles.size()); for (int i = 0; i < p_handles.size(); i++) { - h.write[current_size + i] = p_handles[i]; + handle_list.write[current_size + i] = p_handles[i]; } if (!p_ids.is_empty()) { - Vector<int> &ids = p_secondary ? secondary_handle_ids : handle_ids; - current_size = ids.size(); - ids.resize(current_size + p_ids.size()); + current_size = id_list.size(); + id_list.resize(current_size + p_ids.size()); for (int i = 0; i < p_ids.size(); i++) { - ids.write[current_size + i] = p_ids[i]; + id_list.write[current_size + i] = p_ids[i]; } } } -void EditorNode3DGizmo::add_solid_box(Ref<Material> &p_material, Vector3 p_size, Vector3 p_position, const Transform3D &p_xform) { +void EditorNode3DGizmo::add_solid_box(const Ref<Material> &p_material, Vector3 p_size, Vector3 p_position, const Transform3D &p_xform) { ERR_FAIL_COND(!spatial_node); BoxMesh box_mesh; @@ -574,8 +585,9 @@ bool EditorNode3DGizmo::intersect_frustum(const Camera3D *p_camera, const Vector return false; } -void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id) { +void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id, bool &r_secondary) { r_id = -1; + r_secondary = false; ERR_FAIL_COND(!spatial_node); ERR_FAIL_COND(!valid); @@ -587,7 +599,7 @@ void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 Transform3D camera_xform = p_camera->get_global_transform(); Transform3D t = spatial_node->get_global_transform(); if (billboard_handle) { - t.set_look_at(t.origin, t.origin - camera_xform.basis.get_axis(2), camera_xform.basis.get_axis(1)); + t.set_look_at(t.origin, t.origin - camera_xform.basis.get_column(2), camera_xform.basis.get_column(1)); } float min_d = 1e20; @@ -605,6 +617,7 @@ void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 } else { r_id = secondary_handle_ids[i]; } + r_secondary = true; } } } @@ -628,6 +641,7 @@ void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 } else { r_id = handle_ids[i]; } + r_secondary = false; } } } @@ -661,7 +675,7 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, Transform3D orig_camera_transform = p_camera->get_camera_transform(); if (!orig_camera_transform.origin.is_equal_approx(t.origin) && - ABS(orig_camera_transform.basis.get_axis(Vector3::AXIS_Z).dot(Vector3(0, 1, 0))) < 0.99) { + ABS(orig_camera_transform.basis.get_column(Vector3::AXIS_Z).dot(Vector3(0, 1, 0))) < 0.99) { p_camera->look_at(t.origin); } @@ -685,13 +699,13 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, } if (collision_segments.size()) { - Plane camp(-p_camera->get_transform().basis.get_axis(2).normalized(), p_camera->get_transform().origin); + Plane camp(-p_camera->get_transform().basis.get_column(2).normalized(), p_camera->get_transform().origin); int vc = collision_segments.size(); const Vector3 *vptr = collision_segments.ptr(); Transform3D t = spatial_node->get_global_transform(); if (billboard_handle) { - t.set_look_at(t.origin, t.origin - p_camera->get_transform().basis.get_axis(2), p_camera->get_transform().basis.get_axis(1)); + t.set_look_at(t.origin, t.origin - p_camera->get_transform().basis.get_column(2), p_camera->get_transform().basis.get_column(1)); } Vector3 cp; @@ -738,7 +752,7 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, Transform3D gt = spatial_node->get_global_transform(); if (billboard_handle) { - gt.set_look_at(gt.origin, gt.origin - p_camera->get_transform().basis.get_axis(2), p_camera->get_transform().basis.get_axis(1)); + gt.set_look_at(gt.origin, gt.origin - p_camera->get_transform().basis.get_column(2), p_camera->get_transform().basis.get_column(1)); } Transform3D ai = gt.affine_inverse(); @@ -796,6 +810,7 @@ void EditorNode3DGizmo::transform() { } void EditorNode3DGizmo::free() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); ERR_FAIL_COND(!spatial_node); ERR_FAIL_COND(!valid); @@ -830,8 +845,8 @@ void EditorNode3DGizmo::_bind_methods() { ClassDB::bind_method(D_METHOD("add_collision_triangles", "triangles"), &EditorNode3DGizmo::add_collision_triangles); ClassDB::bind_method(D_METHOD("add_unscaled_billboard", "material", "default_scale", "modulate"), &EditorNode3DGizmo::add_unscaled_billboard, DEFVAL(1), DEFVAL(Color(1, 1, 1))); ClassDB::bind_method(D_METHOD("add_handles", "handles", "material", "ids", "billboard", "secondary"), &EditorNode3DGizmo::add_handles, DEFVAL(false), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("set_spatial_node", "node"), &EditorNode3DGizmo::_set_spatial_node); - ClassDB::bind_method(D_METHOD("get_spatial_node"), &EditorNode3DGizmo::get_spatial_node); + ClassDB::bind_method(D_METHOD("set_node_3d", "node"), &EditorNode3DGizmo::_set_node_3d); + ClassDB::bind_method(D_METHOD("get_node_3d"), &EditorNode3DGizmo::get_node_3d); ClassDB::bind_method(D_METHOD("get_plugin"), &EditorNode3DGizmo::get_plugin); ClassDB::bind_method(D_METHOD("clear"), &EditorNode3DGizmo::clear); ClassDB::bind_method(D_METHOD("set_hidden", "hidden"), &EditorNode3DGizmo::set_hidden); @@ -839,12 +854,12 @@ void EditorNode3DGizmo::_bind_methods() { ClassDB::bind_method(D_METHOD("get_subgizmo_selection"), &EditorNode3DGizmo::get_subgizmo_selection); GDVIRTUAL_BIND(_redraw); - GDVIRTUAL_BIND(_get_handle_name, "id"); - GDVIRTUAL_BIND(_is_handle_highlighted, "id"); + GDVIRTUAL_BIND(_get_handle_name, "id", "secondary"); + GDVIRTUAL_BIND(_is_handle_highlighted, "id", "secondary"); - GDVIRTUAL_BIND(_get_handle_value, "id"); - GDVIRTUAL_BIND(_set_handle, "id", "camera", "point"); - GDVIRTUAL_BIND(_commit_handle, "id", "restore", "cancel"); + GDVIRTUAL_BIND(_get_handle_value, "id", "secondary"); + GDVIRTUAL_BIND(_set_handle, "id", "secondary", "camera", "point"); + GDVIRTUAL_BIND(_commit_handle, "id", "secondary", "restore", "cancel"); GDVIRTUAL_BIND(_subgizmos_intersect_ray, "camera", "point"); GDVIRTUAL_BIND(_subgizmos_intersect_frustum, "camera", "frustum"); @@ -873,7 +888,7 @@ EditorNode3DGizmo::~EditorNode3DGizmo() { ///// void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color &p_color, bool p_billboard, bool p_on_top, bool p_use_vertex_color) { - Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6)); + Color instantiated_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/instantiated"); Vector<Ref<StandardMaterial3D>> mats; @@ -915,7 +930,7 @@ void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color } void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top, const Color &p_albedo) { - Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6)); + Color instantiated_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/instantiated"); Vector<Ref<StandardMaterial3D>> icons; @@ -1003,8 +1018,9 @@ Ref<StandardMaterial3D> EditorNode3DGizmoPlugin::get_material(const String &p_na } String EditorNode3DGizmoPlugin::get_gizmo_name() const { - if (get_script_instance() && get_script_instance()->has_method("_get_gizmo_name")) { - return get_script_instance()->call("_get_gizmo_name"); + String ret; + if (GDVIRTUAL_CALL(_get_gizmo_name, ret)) { + return ret; } WARN_PRINT_ONCE("A 3D editor gizmo has no name defined (it will appear as \"Unnamed Gizmo\" in the \"View > Gizmos\" menu). To resolve this, override the `_get_gizmo_name()` function to return a String in the script that extends EditorNode3DGizmoPlugin."); @@ -1012,8 +1028,9 @@ String EditorNode3DGizmoPlugin::get_gizmo_name() const { } int EditorNode3DGizmoPlugin::get_priority() const { - if (get_script_instance() && get_script_instance()->has_method("_get_priority")) { - return get_script_instance()->call("_get_priority"); + int ret; + if (GDVIRTUAL_CALL(_get_priority, ret)) { + return ret; } return 0; } @@ -1030,7 +1047,7 @@ Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) { } ref->set_plugin(this); - ref->set_spatial_node(p_spatial); + ref->set_node_3d(p_spatial); ref->set_hidden(current_state == HIDDEN); current_gizmos.push_back(ref.ptr()); @@ -1054,27 +1071,24 @@ void EditorNode3DGizmoPlugin::_bind_methods() { GDVIRTUAL_BIND(_is_selectable_when_hidden); GDVIRTUAL_BIND(_redraw, "gizmo"); - GDVIRTUAL_BIND(_get_handle_name, "gizmo", "handle_id"); - GDVIRTUAL_BIND(_is_handle_highlighted, "gizmo", "handle_id"); - GDVIRTUAL_BIND(_get_handle_value, "gizmo", "handle_id"); + GDVIRTUAL_BIND(_get_handle_name, "gizmo", "handle_id", "secondary"); + GDVIRTUAL_BIND(_is_handle_highlighted, "gizmo", "handle_id", "secondary"); + GDVIRTUAL_BIND(_get_handle_value, "gizmo", "handle_id", "secondary"); - GDVIRTUAL_BIND(_set_handle, "gizmo", "handle_id", "camera", "screen_pos"); - GDVIRTUAL_BIND(_commit_handle, "gizmo", "handle_id", "restore", "cancel"); + GDVIRTUAL_BIND(_set_handle, "gizmo", "handle_id", "secondary", "camera", "screen_pos"); + GDVIRTUAL_BIND(_commit_handle, "gizmo", "handle_id", "secondary", "restore", "cancel"); GDVIRTUAL_BIND(_subgizmos_intersect_ray, "gizmo", "camera", "screen_pos"); GDVIRTUAL_BIND(_subgizmos_intersect_frustum, "gizmo", "camera", "frustum_planes"); GDVIRTUAL_BIND(_get_subgizmo_transform, "gizmo", "subgizmo_id"); GDVIRTUAL_BIND(_set_subgizmo_transform, "gizmo", "subgizmo_id", "transform"); GDVIRTUAL_BIND(_commit_subgizmos, "gizmo", "ids", "restores", "cancel"); - ; } bool EditorNode3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { - bool success; - if (GDVIRTUAL_CALL(_has_gizmo, p_spatial, success)) { - return success; - } - return false; + bool success = false; + GDVIRTUAL_CALL(_has_gizmo, p_spatial, success); + return success; } Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) { @@ -1091,86 +1105,68 @@ Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) } bool EditorNode3DGizmoPlugin::can_be_hidden() const { - bool ret; - if (GDVIRTUAL_CALL(_can_be_hidden, ret)) { - return ret; - } - return true; + bool ret = true; + GDVIRTUAL_CALL(_can_be_hidden, ret); + return ret; } bool EditorNode3DGizmoPlugin::is_selectable_when_hidden() const { - bool ret; - if (GDVIRTUAL_CALL(_is_selectable_when_hidden, ret)) { - return ret; - } - return false; + bool ret = false; + GDVIRTUAL_CALL(_is_selectable_when_hidden, ret); + return ret; } void EditorNode3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { GDVIRTUAL_CALL(_redraw, p_gizmo); } -bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const { - bool ret; - if (GDVIRTUAL_CALL(_is_handle_highlighted, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret)) { - return ret; - } - return false; +bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + bool ret = false; + GDVIRTUAL_CALL(_is_handle_highlighted, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, ret); + return ret; } -String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { String ret; - if (GDVIRTUAL_CALL(_get_handle_name, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret)) { - return ret; - } - return ""; + GDVIRTUAL_CALL(_get_handle_name, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, ret); + return ret; } -Variant EditorNode3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { +Variant EditorNode3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { Variant ret; - if (GDVIRTUAL_CALL(_get_handle_value, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret)) { - return ret; - } - return Variant(); + GDVIRTUAL_CALL(_get_handle_value, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, ret); + return ret; } -void EditorNode3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - GDVIRTUAL_CALL(_set_handle, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_camera, p_point); +void EditorNode3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + GDVIRTUAL_CALL(_set_handle, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, p_camera, p_point); } -void EditorNode3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - GDVIRTUAL_CALL(_commit_handle, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_restore, p_cancel); +void EditorNode3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + GDVIRTUAL_CALL(_commit_handle, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, p_restore, p_cancel); } int EditorNode3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { - int ret; - if (GDVIRTUAL_CALL(_subgizmos_intersect_ray, Ref<EditorNode3DGizmo>(p_gizmo), p_camera, p_point, ret)) { - return ret; - } - return -1; + int ret = -1; + GDVIRTUAL_CALL(_subgizmos_intersect_ray, Ref<EditorNode3DGizmo>(p_gizmo), p_camera, p_point, ret); + return ret; } Vector<int> EditorNode3DGizmoPlugin::subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const { - TypedArray<Transform3D> frustum; + TypedArray<Plane> frustum; frustum.resize(p_frustum.size()); for (int i = 0; i < p_frustum.size(); i++) { frustum[i] = p_frustum[i]; } Vector<int> ret; - if (GDVIRTUAL_CALL(_subgizmos_intersect_frustum, Ref<EditorNode3DGizmo>(p_gizmo), p_camera, frustum, ret)) { - return ret; - } - - return Vector<int>(); + GDVIRTUAL_CALL(_subgizmos_intersect_frustum, Ref<EditorNode3DGizmo>(p_gizmo), p_camera, frustum, ret); + return ret; } Transform3D EditorNode3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { Transform3D ret; - if (GDVIRTUAL_CALL(_get_subgizmo_transform, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret)) { - return ret; - } - - return Transform3D(); + GDVIRTUAL_CALL(_get_subgizmo_transform, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret); + return ret; } void EditorNode3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { @@ -1209,7 +1205,7 @@ EditorNode3DGizmoPlugin::EditorNode3DGizmoPlugin() { EditorNode3DGizmoPlugin::~EditorNode3DGizmoPlugin() { for (int i = 0; i < current_gizmos.size(); ++i) { current_gizmos[i]->set_plugin(nullptr); - current_gizmos[i]->get_spatial_node()->remove_gizmo(current_gizmos[i]); + current_gizmos[i]->get_node_3d()->remove_gizmo(current_gizmos[i]); } if (Node3DEditor::get_singleton()) { Node3DEditor::get_singleton()->update_all_gizmos(); @@ -1244,7 +1240,7 @@ int Light3DGizmoPlugin::get_priority() const { return -1; } -String Light3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String Light3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { if (p_id == 0) { return "Radius"; } else { @@ -1252,8 +1248,8 @@ String Light3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int } } -Variant Light3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node()); +Variant Light3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_node_3d()); if (p_id == 0) { return light->get_param(Light3D::PARAM_RANGE); } @@ -1288,11 +1284,11 @@ static float _find_closest_angle_to_half_pi_arc(const Vector3 &p_from, const Vec //min_p = p_arc_xform.affine_inverse().xform(min_p); float a = (Math_PI * 0.5) - Vector2(min_p.x, -min_p.z).angle(); - return Math::rad2deg(a); + return Math::rad_to_deg(a); } -void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node()); +void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_node_3d()); Transform3D gt = light->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -1316,7 +1312,7 @@ void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, light->set_param(Light3D::PARAM_RANGE, d); } else if (Object::cast_to<OmniLight3D>(light)) { - Plane cp = Plane(p_camera->get_transform().basis.get_axis(2), gt.origin); + Plane cp = Plane(p_camera->get_transform().basis.get_column(2), gt.origin); Vector3 inters; if (cp.intersects_ray(ray_from, ray_dir, &inters)) { @@ -1335,19 +1331,19 @@ void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, } } -void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node()); +void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_node_3d()); if (p_cancel) { light->set_param(p_id == 0 ? Light3D::PARAM_RANGE : Light3D::PARAM_SPOT_ANGLE, p_restore); } else if (p_id == 0) { - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change Light Radius")); ur->add_do_method(light, "set_param", Light3D::PARAM_RANGE, light->get_param(Light3D::PARAM_RANGE)); ur->add_undo_method(light, "set_param", Light3D::PARAM_RANGE, p_restore); ur->commit_action(); } else if (p_id == 1) { - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change Light Radius")); ur->add_do_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, light->get_param(Light3D::PARAM_SPOT_ANGLE)); ur->add_undo_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, p_restore); @@ -1356,9 +1352,10 @@ void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_i } void Light3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node()); + Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_node_3d()); - Color color = light->get_color(); + Color color = light->get_color().srgb_to_linear() * light->get_correlated_color().srgb_to_linear(); + color = color.linear_to_srgb(); // Make the gizmo color as bright as possible for better visibility color.set_hsv(color.get_h(), color.get_s(), 1); @@ -1414,8 +1411,8 @@ void Light3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { for (int i = 0; i < 120; i++) { // Create a circle - const float ra = Math::deg2rad((float)(i * 3)); - const float rb = Math::deg2rad((float)((i + 1) * 3)); + const float ra = Math::deg_to_rad((float)(i * 3)); + const float rb = Math::deg_to_rad((float)((i + 1) * 3)); const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; @@ -1451,13 +1448,13 @@ void Light3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { SpotLight3D *sl = Object::cast_to<SpotLight3D>(light); float r = sl->get_param(Light3D::PARAM_RANGE); - float w = r * Math::sin(Math::deg2rad(sl->get_param(Light3D::PARAM_SPOT_ANGLE))); - float d = r * Math::cos(Math::deg2rad(sl->get_param(Light3D::PARAM_SPOT_ANGLE))); + float w = r * Math::sin(Math::deg_to_rad(sl->get_param(Light3D::PARAM_SPOT_ANGLE))); + float d = r * Math::cos(Math::deg_to_rad(sl->get_param(Light3D::PARAM_SPOT_ANGLE))); for (int i = 0; i < 120; i++) { // Draw a circle - const float ra = Math::deg2rad((float)(i * 3)); - const float rb = Math::deg2rad((float)((i + 1) * 3)); + const float ra = Math::deg_to_rad((float)(i * 3)); + const float rb = Math::deg_to_rad((float)((i + 1) * 3)); const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w; const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w; @@ -1477,9 +1474,10 @@ void Light3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_lines(points_primary, material_primary, false, color); p_gizmo->add_lines(points_secondary, material_secondary, false, color); - Vector<Vector3> handles; - handles.push_back(Vector3(0, 0, -r)); - handles.push_back(Vector3(w, 0, -d)); + Vector<Vector3> handles = { + Vector3(0, 0, -r), + Vector3(w, 0, -d) + }; p_gizmo->add_handles(handles, get_material("handles")); p_gizmo->add_unscaled_billboard(icon, 0.05, color); @@ -1493,6 +1491,9 @@ AudioStreamPlayer3DGizmoPlugin::AudioStreamPlayer3DGizmoPlugin() { create_icon_material("stream_player_3d_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("Gizmo3DSamplePlayer"), SNAME("EditorIcons"))); create_material("stream_player_3d_material_primary", gizmo_color); create_material("stream_player_3d_material_secondary", gizmo_color * Color(1, 1, 1, 0.35)); + // Enable vertex colors for the billboard material as the gizmo color depends on the + // AudioStreamPlayer3D attenuation type and source (Unit Size or Max Distance). + create_material("stream_player_3d_material_billboard", Color(1, 1, 1), true, false, true); create_handle_material("handles"); } @@ -1508,17 +1509,17 @@ int AudioStreamPlayer3DGizmoPlugin::get_priority() const { return -1; } -String AudioStreamPlayer3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String AudioStreamPlayer3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { return "Emission Radius"; } -Variant AudioStreamPlayer3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node()); +Variant AudioStreamPlayer3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_node_3d()); return player->get_emission_angle(); } -void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node()); +void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_node_3d()); Transform3D gt = player->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -1534,8 +1535,8 @@ void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo float closest_angle = 1e20; for (int i = 0; i < 180; i++) { - float a = Math::deg2rad((float)i); - float an = Math::deg2rad((float)(i + 1)); + float a = Math::deg_to_rad((float)i); + float an = Math::deg_to_rad((float)(i + 1)); Vector3 from(Math::sin(a), 0, -Math::cos(a)); Vector3 to(Math::sin(an), 0, -Math::cos(an)); @@ -1554,14 +1555,14 @@ void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo } } -void AudioStreamPlayer3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node()); +void AudioStreamPlayer3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_node_3d()); if (p_cancel) { player->set_emission_angle(p_restore); } else { - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change AudioStreamPlayer3D Emission Angle")); ur->add_do_method(player, "set_emission_angle", player->get_emission_angle()); ur->add_undo_method(player, "set_emission_angle", p_restore); @@ -1570,16 +1571,98 @@ void AudioStreamPlayer3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gi } void AudioStreamPlayer3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - const AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node()); + const AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); const Ref<Material> icon = get_material("stream_player_3d_icon", p_gizmo); + if (player->get_attenuation_model() != AudioStreamPlayer3D::ATTENUATION_DISABLED || player->get_max_distance() > CMP_EPSILON) { + // Draw a circle to represent sound volume attenuation. + // Use only a billboard circle to represent radius. + // This helps distinguish AudioStreamPlayer3D gizmos from OmniLight3D gizmos. + const Ref<Material> lines_billboard_material = get_material("stream_player_3d_material_billboard", p_gizmo); + + // Soft distance cap varies depending on attenuation model, as some will fade out more aggressively than others. + // Multipliers were empirically determined through testing. + float soft_multiplier; + switch (player->get_attenuation_model()) { + case AudioStreamPlayer3D::ATTENUATION_INVERSE_DISTANCE: + soft_multiplier = 12.0; + break; + case AudioStreamPlayer3D::ATTENUATION_INVERSE_SQUARE_DISTANCE: + soft_multiplier = 4.0; + break; + case AudioStreamPlayer3D::ATTENUATION_LOGARITHMIC: + soft_multiplier = 3.25; + break; + default: + // Ensures Max Distance's radius visualization is not capped by Unit Size + // (when the attenuation mode is Disabled). + soft_multiplier = 10000.0; + break; + } + + // Draw the distance at which the sound can be reasonably heard. + // This can be either a hard distance cap with the Max Distance property (if set above 0.0), + // or a soft distance cap with the Unit Size property (sound never reaches true zero). + // When Max Distance is 0.0, `r` represents the distance above which the + // sound can't be heard in *most* (but not all) scenarios. + float r; + if (player->get_max_distance() > CMP_EPSILON) { + r = MIN(player->get_unit_size() * soft_multiplier, player->get_max_distance()); + } else { + r = player->get_unit_size() * soft_multiplier; + } + Vector<Vector3> points_billboard; + + for (int i = 0; i < 120; i++) { + // Create a circle. + const float ra = Math::deg_to_rad((float)(i * 3)); + const float rb = Math::deg_to_rad((float)((i + 1) * 3)); + const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; + const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; + + // Draw a billboarded circle. + points_billboard.push_back(Vector3(a.x, a.y, 0)); + points_billboard.push_back(Vector3(b.x, b.y, 0)); + } + + Color color; + switch (player->get_attenuation_model()) { + // Pick cold colors for all attenuation models (except Disabled), + // so that soft caps can be easily distinguished from hard caps + // (which use warm colors). + case AudioStreamPlayer3D::ATTENUATION_INVERSE_DISTANCE: + color = Color(0.4, 0.8, 1); + break; + case AudioStreamPlayer3D::ATTENUATION_INVERSE_SQUARE_DISTANCE: + color = Color(0.4, 0.5, 1); + break; + case AudioStreamPlayer3D::ATTENUATION_LOGARITHMIC: + color = Color(0.4, 0.2, 1); + break; + default: + // Disabled attenuation mode. + // This is never reached when Max Distance is 0, but the + // hue-inverted form of this color will be used if Max Distance is greater than 0. + color = Color(1, 1, 1); + break; + } + + if (player->get_max_distance() > CMP_EPSILON) { + // Sound is hard-capped by max distance. The attenuation model still matters, + // so invert the hue of the color that was chosen above. + color.set_h(color.get_h() + 0.5); + } + + p_gizmo->add_lines(points_billboard, lines_billboard_material, true, color); + } + if (player->is_emission_angle_enabled()) { const float pc = player->get_emission_angle(); - const float ofs = -Math::cos(Math::deg2rad(pc)); - const float radius = Math::sin(Math::deg2rad(pc)); + const float ofs = -Math::cos(Math::deg_to_rad(pc)); + const float radius = Math::sin(Math::deg_to_rad(pc)); Vector<Vector3> points_primary; points_primary.resize(200); @@ -1614,7 +1697,7 @@ void AudioStreamPlayer3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_lines(points_secondary, material_secondary); Vector<Vector3> handles; - const float ha = Math::deg2rad(player->get_emission_angle()); + const float ha = Math::deg_to_rad(player->get_emission_angle()); handles.push_back(Vector3(Math::sin(ha), 0, -Math::cos(ha))); p_gizmo->add_handles(handles, get_material("handles")); } @@ -1625,7 +1708,7 @@ void AudioStreamPlayer3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ////// AudioListener3DGizmoPlugin::AudioListener3DGizmoPlugin() { - create_icon_material("audio_listener_3d_icon", Node3DEditor::get_singleton()->get_theme_icon("GizmoAudioListener3D", "EditorIcons")); + create_icon_material("audio_listener_3d_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoAudioListener3D"), SNAME("EditorIcons"))); } bool AudioListener3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { @@ -1654,6 +1737,24 @@ Camera3DGizmoPlugin::Camera3DGizmoPlugin() { create_handle_material("handles"); } +Size2i Camera3DGizmoPlugin::_get_viewport_size(Camera3D *p_camera) { + Viewport *viewport = p_camera->get_viewport(); + + Window *window = Object::cast_to<Window>(viewport); + if (window) { + return window->get_size(); + } + + SubViewport *sub_viewport = Object::cast_to<SubViewport>(viewport); + ERR_FAIL_NULL_V(sub_viewport, Size2i()); + + if (sub_viewport == EditorNode::get_singleton()->get_scene_root()) { + return Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); + } + + return sub_viewport->get_size(); +} + bool Camera3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { return Object::cast_to<Camera3D>(p_spatial) != nullptr; } @@ -1666,8 +1767,8 @@ int Camera3DGizmoPlugin::get_priority() const { return -1; } -String Camera3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { - Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); +String Camera3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d()); if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) { return "FOV"; @@ -1676,8 +1777,8 @@ String Camera3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, in } } -Variant Camera3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); +Variant Camera3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d()); if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) { return camera->get_fov(); @@ -1686,8 +1787,8 @@ Variant Camera3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, } } -void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); +void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d()); Transform3D gt = camera->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -1704,7 +1805,7 @@ void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, } else { Vector3 ra, rb; Geometry3D::get_closest_points_between_segments(Vector3(0, 0, -1), Vector3(4096, 0, -1), s[0], s[1], ra, rb); - float d = ra.x * 2.0; + float d = ra.x * 2; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } @@ -1715,14 +1816,14 @@ void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, } } -void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); +void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d()); if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) { if (p_cancel) { camera->set("fov", p_restore); } else { - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change Camera FOV")); ur->add_do_property(camera, "fov", camera->get_fov()); ur->add_undo_property(camera, "fov", p_restore); @@ -1733,7 +1834,7 @@ void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_ if (p_cancel) { camera->set("size", p_restore); } else { - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change Camera Size")); ur->add_do_property(camera, "size", camera->get_size()); ur->add_undo_property(camera, "size", p_restore); @@ -1743,7 +1844,7 @@ void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_ } void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); + Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -1752,6 +1853,10 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Ref<Material> material = get_material("camera_material", p_gizmo); + const Size2i viewport_size = _get_viewport_size(camera); + const real_t viewport_aspect = viewport_size.x > 0 && viewport_size.y > 0 ? viewport_size.aspect() : 1.0; + const Size2 size_factor = viewport_aspect > 1.0 ? Size2(1.0, 1.0 / viewport_aspect) : Size2(viewport_aspect, 1.0); + #define ADD_TRIANGLE(m_a, m_b, m_c) \ { \ lines.push_back(m_a); \ @@ -1779,10 +1884,11 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { // The real FOV is halved for accurate representation float fov = camera->get_fov() / 2.0; - Vector3 side = Vector3(Math::sin(Math::deg2rad(fov)), 0, -Math::cos(Math::deg2rad(fov))); - Vector3 nside = side; - nside.x = -nside.x; - Vector3 up = Vector3(0, side.x, 0); + const float hsize = Math::sin(Math::deg_to_rad(fov)); + const float depth = -Math::cos(Math::deg_to_rad(fov)); + Vector3 side = Vector3(hsize * size_factor.x, 0, depth); + Vector3 nside = Vector3(-side.x, side.y, side.z); + Vector3 up = Vector3(0, hsize * size_factor.y, 0); ADD_TRIANGLE(Vector3(), side + up, side - up); ADD_TRIANGLE(Vector3(), nside + up, nside - up); @@ -1790,18 +1896,18 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ADD_TRIANGLE(Vector3(), side - up, nside - up); handles.push_back(side); - side.x *= 0.25; - nside.x *= 0.25; - Vector3 tup(0, up.y * 3 / 2, side.z); + side.x = MIN(side.x, hsize * 0.25); + nside.x = -side.x; + Vector3 tup(0, up.y + hsize / 2, side.z); ADD_TRIANGLE(tup, side + up, nside + up); - } break; + case Camera3D::PROJECTION_ORTHOGONAL: { float size = camera->get_size(); float hsize = size * 0.5; - Vector3 right(hsize, 0, 0); - Vector3 up(0, hsize, 0); + Vector3 right(hsize * size_factor.x, 0, 0); + Vector3 up(0, hsize * size_factor.y, 0); Vector3 back(0, 0, -1.0); Vector3 front(0, 0, 0); @@ -1812,18 +1918,19 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { handles.push_back(right + back); - right.x *= 0.25; - Vector3 tup(0, up.y * 3 / 2, back.z); + right.x = MIN(right.x, hsize * 0.25); + Vector3 tup(0, up.y + hsize / 2, back.z); ADD_TRIANGLE(tup, right + up + back, -right + up + back); } break; + case Camera3D::PROJECTION_FRUSTUM: { float hsize = camera->get_size() / 2.0; Vector3 side = Vector3(hsize, 0, -camera->get_near()).normalized(); - Vector3 nside = side; - nside.x = -nside.x; - Vector3 up = Vector3(0, side.x, 0); + side.x *= size_factor.x; + Vector3 nside = Vector3(-side.x, side.y, side.z); + Vector3 up = Vector3(0, hsize * size_factor.y, 0); Vector3 offset = Vector3(camera->get_frustum_offset().x, camera->get_frustum_offset().y, 0.0); ADD_TRIANGLE(Vector3(), side + up + offset, side - up + offset); @@ -1831,18 +1938,22 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ADD_TRIANGLE(Vector3(), side + up + offset, nside + up + offset); ADD_TRIANGLE(Vector3(), side - up + offset, nside - up + offset); - side.x *= 0.25; - nside.x *= 0.25; - Vector3 tup(0, up.y * 3 / 2, side.z); + side.x = MIN(side.x, hsize * 0.25); + nside.x = -side.x; + Vector3 tup(0, up.y + hsize / 2, side.z); ADD_TRIANGLE(tup + offset, side + up + offset, nside + up + offset); - } + } break; } #undef ADD_TRIANGLE #undef ADD_QUAD p_gizmo->add_lines(lines, material); - p_gizmo->add_handles(handles, get_material("handles")); + p_gizmo->add_collision_segments(lines); + + if (!handles.is_empty()) { + p_gizmo->add_handles(handles, get_material("handles")); + } } ////// @@ -1851,7 +1962,7 @@ MeshInstance3DGizmoPlugin::MeshInstance3DGizmoPlugin() { } bool MeshInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { - return Object::cast_to<MeshInstance3D>(p_spatial) != nullptr && Object::cast_to<SoftDynamicBody3D>(p_spatial) == nullptr; + return Object::cast_to<MeshInstance3D>(p_spatial) != nullptr && Object::cast_to<SoftBody3D>(p_spatial) == nullptr; } String MeshInstance3DGizmoPlugin::get_gizmo_name() const { @@ -1867,7 +1978,7 @@ bool MeshInstance3DGizmoPlugin::can_be_hidden() const { } void MeshInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - MeshInstance3D *mesh = Object::cast_to<MeshInstance3D>(p_gizmo->get_spatial_node()); + MeshInstance3D *mesh = Object::cast_to<MeshInstance3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -1887,6 +1998,7 @@ void MeshInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { OccluderInstance3DGizmoPlugin::OccluderInstance3DGizmoPlugin() { create_material("line_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder", Color(0.8, 0.5, 1))); + create_handle_material("handles"); } bool OccluderInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { @@ -1901,8 +2013,191 @@ int OccluderInstance3DGizmoPlugin::get_priority() const { return -1; } +String OccluderInstance3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + const OccluderInstance3D *cs = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d()); + + Ref<Occluder3D> o = cs->get_occluder(); + if (o.is_null()) { + return ""; + } + + if (Object::cast_to<SphereOccluder3D>(*o)) { + return "Radius"; + } + + if (Object::cast_to<BoxOccluder3D>(*o) || Object::cast_to<QuadOccluder3D>(*o)) { + return "Size"; + } + + return ""; +} + +Variant OccluderInstance3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d()); + + Ref<Occluder3D> o = oi->get_occluder(); + if (o.is_null()) { + return Variant(); + } + + if (Object::cast_to<SphereOccluder3D>(*o)) { + Ref<SphereOccluder3D> so = o; + return so->get_radius(); + } + + if (Object::cast_to<BoxOccluder3D>(*o)) { + Ref<BoxOccluder3D> bo = o; + return bo->get_size(); + } + + if (Object::cast_to<QuadOccluder3D>(*o)) { + Ref<QuadOccluder3D> qo = o; + return qo->get_size(); + } + + return Variant(); +} + +void OccluderInstance3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d()); + + Ref<Occluder3D> o = oi->get_occluder(); + if (o.is_null()) { + return; + } + + Transform3D gt = oi->get_global_transform(); + Transform3D gi = gt.affine_inverse(); + + Vector3 ray_from = p_camera->project_ray_origin(p_point); + Vector3 ray_dir = p_camera->project_ray_normal(p_point); + + Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) }; + + bool snap_enabled = Node3DEditor::get_singleton()->is_snap_enabled(); + float snap = Node3DEditor::get_singleton()->get_translate_snap(); + + if (Object::cast_to<SphereOccluder3D>(*o)) { + Ref<SphereOccluder3D> so = o; + Vector3 ra, rb; + Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb); + float d = ra.x; + if (snap_enabled) { + d = Math::snapped(d, snap); + } + + if (d < 0.001) { + d = 0.001; + } + + so->set_radius(d); + } + + if (Object::cast_to<BoxOccluder3D>(*o)) { + Vector3 axis; + axis[p_id] = 1.0; + Ref<BoxOccluder3D> bo = o; + Vector3 ra, rb; + Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + float d = ra[p_id] * 2; + if (snap_enabled) { + d = Math::snapped(d, snap); + } + + if (d < 0.001) { + d = 0.001; + } + + Vector3 he = bo->get_size(); + he[p_id] = d; + bo->set_size(he); + } + + if (Object::cast_to<QuadOccluder3D>(*o)) { + Ref<QuadOccluder3D> qo = o; + Plane p = Plane(Vector3(0.0f, 0.0f, 1.0f), 0.0f); + Vector3 intersection; + if (!p.intersects_segment(sg[0], sg[1], &intersection)) { + return; + } + + if (p_id == 2) { + Vector2 s = Vector2(intersection.x, intersection.y) * 2.0f; + if (snap_enabled) { + s = s.snapped(Vector2(snap, snap)); + } + s = s.max(Vector2(0.001, 0.001)); + qo->set_size(s); + } else { + float d = intersection[p_id]; + if (snap_enabled) { + d = Math::snapped(d, snap); + } + + if (d < 0.001) { + d = 0.001; + } + + Vector2 he = qo->get_size(); + he[p_id] = d * 2.0f; + qo->set_size(he); + } + } +} + +void OccluderInstance3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d()); + + Ref<Occluder3D> o = oi->get_occluder(); + if (o.is_null()) { + return; + } + + if (Object::cast_to<SphereOccluder3D>(*o)) { + Ref<SphereOccluder3D> so = o; + if (p_cancel) { + so->set_radius(p_restore); + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change Sphere Shape Radius")); + ur->add_do_method(so.ptr(), "set_radius", so->get_radius()); + ur->add_undo_method(so.ptr(), "set_radius", p_restore); + ur->commit_action(); + } + + if (Object::cast_to<BoxOccluder3D>(*o)) { + Ref<BoxOccluder3D> bo = o; + if (p_cancel) { + bo->set_size(p_restore); + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change Box Shape Size")); + ur->add_do_method(bo.ptr(), "set_size", bo->get_size()); + ur->add_undo_method(bo.ptr(), "set_size", p_restore); + ur->commit_action(); + } + + if (Object::cast_to<QuadOccluder3D>(*o)) { + Ref<QuadOccluder3D> qo = o; + if (p_cancel) { + qo->set_size(p_restore); + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change Box Shape Size")); + ur->add_do_method(qo.ptr(), "set_size", qo->get_size()); + ur->add_undo_method(qo.ptr(), "set_size", p_restore); + ur->commit_action(); + } +} + void OccluderInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - OccluderInstance3D *occluder_instance = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node()); + OccluderInstance3D *occluder_instance = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -1918,6 +2213,35 @@ void OccluderInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_lines(lines, material); p_gizmo->add_collision_segments(lines); } + + Ref<Material> handles_material = get_material("handles"); + if (Object::cast_to<SphereOccluder3D>(*o)) { + Ref<SphereOccluder3D> so = o; + float r = so->get_radius(); + Vector<Vector3> handles = { Vector3(r, 0, 0) }; + p_gizmo->add_handles(handles, handles_material); + } + + if (Object::cast_to<BoxOccluder3D>(*o)) { + Ref<BoxOccluder3D> bo = o; + + Vector<Vector3> handles; + for (int i = 0; i < 3; i++) { + Vector3 ax; + ax[i] = bo->get_size()[i] / 2; + handles.push_back(ax); + } + + p_gizmo->add_handles(handles, handles_material); + } + + if (Object::cast_to<QuadOccluder3D>(*o)) { + Ref<QuadOccluder3D> qo = o; + Vector2 size = qo->get_size(); + Vector3 s = Vector3(size.x, size.y, 0.0f) / 2.0f; + Vector<Vector3> handles = { Vector3(s.x, 0.0f, 0.0f), Vector3(0.0f, s.y, 0.0f), Vector3(s.x, s.y, 0.0f) }; + p_gizmo->add_handles(handles, handles_material); + } } ///// @@ -1942,7 +2266,7 @@ bool Sprite3DGizmoPlugin::can_be_hidden() const { } void Sprite3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Sprite3D *sprite = Object::cast_to<Sprite3D>(p_gizmo->get_spatial_node()); + Sprite3D *sprite = Object::cast_to<Sprite3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -1954,12 +2278,44 @@ void Sprite3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { /// -Position3DGizmoPlugin::Position3DGizmoPlugin() { +Label3DGizmoPlugin::Label3DGizmoPlugin() { +} + +bool Label3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<Label3D>(p_spatial) != nullptr; +} + +String Label3DGizmoPlugin::get_gizmo_name() const { + return "Label3D"; +} + +int Label3DGizmoPlugin::get_priority() const { + return -1; +} + +bool Label3DGizmoPlugin::can_be_hidden() const { + return false; +} + +void Label3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + Label3D *label = Object::cast_to<Label3D>(p_gizmo->get_node_3d()); + + p_gizmo->clear(); + + Ref<TriangleMesh> tm = label->generate_triangle_mesh(); + if (tm.is_valid()) { + p_gizmo->add_collision_triangles(tm); + } +} + +/// + +Marker3DGizmoPlugin::Marker3DGizmoPlugin() { pos3d_mesh = Ref<ArrayMesh>(memnew(ArrayMesh)); - cursor_points = Vector<Vector3>(); + Vector<Vector3> cursor_points; Vector<Color> cursor_colors; - const float cs = 0.25; + const float cs = 1.0; // Add more points to create a "hard stop" in the color gradient. cursor_points.push_back(Vector3(+cs, 0, 0)); cursor_points.push_back(Vector3()); @@ -1978,7 +2334,7 @@ Position3DGizmoPlugin::Position3DGizmoPlugin() { // Use the axis color which is brighter for the positive axis. // Use a darkened axis color for the negative axis. - // This makes it possible to see in which direction the Position3D node is rotated + // This makes it possible to see in which direction the Marker3D node is rotated // (which can be important depending on how it's used). const Color color_x = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor")); cursor_colors.push_back(color_x); @@ -2014,28 +2370,41 @@ Position3DGizmoPlugin::Position3DGizmoPlugin() { pos3d_mesh->surface_set_material(0, mat); } -bool Position3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { - return Object::cast_to<Position3D>(p_spatial) != nullptr; +bool Marker3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<Marker3D>(p_spatial) != nullptr; } -String Position3DGizmoPlugin::get_gizmo_name() const { - return "Position3D"; +String Marker3DGizmoPlugin::get_gizmo_name() const { + return "Marker3D"; } -int Position3DGizmoPlugin::get_priority() const { +int Marker3DGizmoPlugin::get_priority() const { return -1; } -void Position3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { +void Marker3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + const Marker3D *marker = Object::cast_to<Marker3D>(p_gizmo->get_node_3d()); + const real_t extents = marker->get_gizmo_extents(); + const Transform3D xform(Basis::from_scale(Vector3(extents, extents, extents))); + p_gizmo->clear(); - p_gizmo->add_mesh(pos3d_mesh); - p_gizmo->add_collision_segments(cursor_points); + p_gizmo->add_mesh(pos3d_mesh, Ref<Material>(), xform); + + const Vector<Vector3> points = { + Vector3(-extents, 0, 0), + Vector3(+extents, 0, 0), + Vector3(0, -extents, 0), + Vector3(0, +extents, 0), + Vector3(0, 0, -extents), + Vector3(0, 0, +extents), + }; + p_gizmo->add_collision_segments(points); } //// PhysicalBone3DGizmoPlugin::PhysicalBone3DGizmoPlugin() { - create_material("joint_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1))); + create_material("joint_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/joint")); } bool PhysicalBone3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { @@ -2053,7 +2422,7 @@ int PhysicalBone3DGizmoPlugin::get_priority() const { void PhysicalBone3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->clear(); - PhysicalBone3D *physical_bone = Object::cast_to<PhysicalBone3D>(p_gizmo->get_spatial_node()); + PhysicalBone3D *physical_bone = Object::cast_to<PhysicalBone3D>(p_gizmo->get_node_3d()); if (!physical_bone) { return; @@ -2168,7 +2537,7 @@ void PhysicalBone3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ///// RayCast3DGizmoPlugin::RayCast3DGizmoPlugin() { - const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + const Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); const float gizmo_value = gizmo_color.get_v(); const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); @@ -2188,7 +2557,7 @@ int RayCast3DGizmoPlugin::get_priority() const { } void RayCast3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - RayCast3D *raycast = Object::cast_to<RayCast3D>(p_gizmo->get_spatial_node()); + RayCast3D *raycast = Object::cast_to<RayCast3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2205,15 +2574,53 @@ void RayCast3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ///// -void SpringArm3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - SpringArm3D *spring_arm = Object::cast_to<SpringArm3D>(p_gizmo->get_spatial_node()); +ShapeCast3DGizmoPlugin::ShapeCast3DGizmoPlugin() { + const Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); + create_material("shape_material", gizmo_color); + const float gizmo_value = gizmo_color.get_v(); + const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); + create_material("shape_material_disabled", gizmo_color_disabled); +} + +bool ShapeCast3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<ShapeCast3D>(p_spatial) != nullptr; +} + +String ShapeCast3DGizmoPlugin::get_gizmo_name() const { + return "ShapeCast3D"; +} + +int ShapeCast3DGizmoPlugin::get_priority() const { + return -1; +} + +void ShapeCast3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + ShapeCast3D *shapecast = Object::cast_to<ShapeCast3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); - Vector<Vector3> lines; + const Ref<StandardMaterial3D> material = shapecast->is_enabled() ? shapecast->get_debug_material() : get_material("shape_material_disabled"); + + p_gizmo->add_lines(shapecast->get_debug_line_vertices(), material); - lines.push_back(Vector3()); - lines.push_back(Vector3(0, 0, 1.0) * spring_arm->get_length()); + if (shapecast->get_shape().is_valid()) { + p_gizmo->add_lines(shapecast->get_debug_shape_vertices(), material); + } + + p_gizmo->add_collision_segments(shapecast->get_debug_line_vertices()); +} + +///// + +void SpringArm3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + SpringArm3D *spring_arm = Object::cast_to<SpringArm3D>(p_gizmo->get_node_3d()); + + p_gizmo->clear(); + + Vector<Vector3> lines = { + Vector3(), + Vector3(0, 0, 1.0) * spring_arm->get_length() + }; Ref<StandardMaterial3D> material = get_material("shape_material", p_gizmo); @@ -2222,7 +2629,7 @@ void SpringArm3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } SpringArm3DGizmoPlugin::SpringArm3DGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); } @@ -2241,7 +2648,7 @@ int SpringArm3DGizmoPlugin::get_priority() const { ///// VehicleWheel3DGizmoPlugin::VehicleWheel3DGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); } @@ -2258,7 +2665,7 @@ int VehicleWheel3DGizmoPlugin::get_priority() const { } void VehicleWheel3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - VehicleWheel3D *car_wheel = Object::cast_to<VehicleWheel3D>(p_gizmo->get_spatial_node()); + VehicleWheel3D *car_wheel = Object::cast_to<VehicleWheel3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2267,8 +2674,8 @@ void VehicleWheel3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { float r = car_wheel->get_radius(); const int skip = 10; for (int i = 0; i <= 360; i += skip) { - float ra = Math::deg2rad((float)i); - float rb = Math::deg2rad((float)i + skip); + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + skip); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; @@ -2311,30 +2718,30 @@ void VehicleWheel3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { /////////// -SoftDynamicBody3DGizmoPlugin::SoftDynamicBody3DGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); +SoftBody3DGizmoPlugin::SoftBody3DGizmoPlugin() { + Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); create_handle_material("handles"); } -bool SoftDynamicBody3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { - return Object::cast_to<SoftDynamicBody3D>(p_spatial) != nullptr; +bool SoftBody3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<SoftBody3D>(p_spatial) != nullptr; } -String SoftDynamicBody3DGizmoPlugin::get_gizmo_name() const { - return "SoftDynamicBody3D"; +String SoftBody3DGizmoPlugin::get_gizmo_name() const { + return "SoftBody3D"; } -int SoftDynamicBody3DGizmoPlugin::get_priority() const { +int SoftBody3DGizmoPlugin::get_priority() const { return -1; } -bool SoftDynamicBody3DGizmoPlugin::is_selectable_when_hidden() const { +bool SoftBody3DGizmoPlugin::is_selectable_when_hidden() const { return true; } -void SoftDynamicBody3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - SoftDynamicBody3D *soft_body = Object::cast_to<SoftDynamicBody3D>(p_gizmo->get_spatial_node()); +void SoftBody3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2370,22 +2777,22 @@ void SoftDynamicBody3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_collision_triangles(tm); } -String SoftDynamicBody3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { - return "SoftDynamicBody3D pin point"; +String SoftBody3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + return "SoftBody3D pin point"; } -Variant SoftDynamicBody3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - SoftDynamicBody3D *soft_body = Object::cast_to<SoftDynamicBody3D>(p_gizmo->get_spatial_node()); +Variant SoftBody3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_node_3d()); return Variant(soft_body->is_point_pinned(p_id)); } -void SoftDynamicBody3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - SoftDynamicBody3D *soft_body = Object::cast_to<SoftDynamicBody3D>(p_gizmo->get_spatial_node()); +void SoftBody3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_node_3d()); soft_body->pin_point_toggle(p_id); } -bool SoftDynamicBody3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const { - SoftDynamicBody3D *soft_body = Object::cast_to<SoftDynamicBody3D>(p_gizmo->get_spatial_node()); +bool SoftBody3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_node_3d()); return soft_body->is_point_pinned(p_id); } @@ -2411,7 +2818,7 @@ int VisibleOnScreenNotifier3DGizmoPlugin::get_priority() const { return -1; } -String VisibleOnScreenNotifier3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String VisibleOnScreenNotifier3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { switch (p_id) { case 0: return "Size X"; @@ -2430,13 +2837,13 @@ String VisibleOnScreenNotifier3DGizmoPlugin::get_handle_name(const EditorNode3DG return ""; } -Variant VisibleOnScreenNotifier3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node()); +Variant VisibleOnScreenNotifier3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_node_3d()); return notifier->get_aabb(); } -void VisibleOnScreenNotifier3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node()); +void VisibleOnScreenNotifier3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_node_3d()); Transform3D gt = notifier->get_global_transform(); @@ -2487,15 +2894,15 @@ void VisibleOnScreenNotifier3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p } } -void VisibleOnScreenNotifier3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node()); +void VisibleOnScreenNotifier3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_node_3d()); if (p_cancel) { notifier->set_aabb(p_restore); return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change Notifier AABB")); ur->add_do_method(notifier, "set_aabb", notifier->get_aabb()); ur->add_undo_method(notifier, "set_aabb", p_restore); @@ -2503,7 +2910,7 @@ void VisibleOnScreenNotifier3DGizmoPlugin::commit_handle(const EditorNode3DGizmo } void VisibleOnScreenNotifier3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node()); + VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2603,7 +3010,7 @@ bool GPUParticles3DGizmoPlugin::is_selectable_when_hidden() const { return true; } -String GPUParticles3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String GPUParticles3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { switch (p_id) { case 0: return "Size X"; @@ -2622,13 +3029,13 @@ String GPUParticles3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_giz return ""; } -Variant GPUParticles3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node()); +Variant GPUParticles3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d()); return particles->get_visibility_aabb(); } -void GPUParticles3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node()); +void GPUParticles3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d()); Transform3D gt = particles->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -2678,15 +3085,15 @@ void GPUParticles3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int } } -void GPUParticles3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node()); +void GPUParticles3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d()); if (p_cancel) { particles->set_visibility_aabb(p_restore); return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change Particles AABB")); ur->add_do_method(particles, "set_visibility_aabb", particles->get_visibility_aabb()); ur->add_undo_method(particles, "set_visibility_aabb", p_restore); @@ -2694,7 +3101,7 @@ void GPUParticles3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, } void GPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node()); + GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -2744,10 +3151,15 @@ void GPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { //// GPUParticlesCollision3DGizmoPlugin::GPUParticlesCollision3DGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particle_collision", Color(0.5, 0.7, 1)); - create_material("shape_material", gizmo_color); - gizmo_color.a = 0.15; - create_material("shape_material_internal", gizmo_color); + Color gizmo_color_attractor = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particle_attractor", Color(1, 0.7, 0.5)); + create_material("shape_material_attractor", gizmo_color_attractor); + gizmo_color_attractor.a = 0.15; + create_material("shape_material_attractor_internal", gizmo_color_attractor); + + Color gizmo_color_collision = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particle_collision", Color(0.5, 0.7, 1)); + create_material("shape_material_collision", gizmo_color_collision); + gizmo_color_collision.a = 0.15; + create_material("shape_material_collision_internal", gizmo_color_collision); create_handle_material("handles"); } @@ -2764,36 +3176,36 @@ int GPUParticlesCollision3DGizmoPlugin::get_priority() const { return -1; } -String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { - const Node3D *cs = p_gizmo->get_spatial_node(); +String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + const Node3D *cs = p_gizmo->get_node_3d(); if (Object::cast_to<GPUParticlesCollisionSphere3D>(cs) || Object::cast_to<GPUParticlesAttractorSphere3D>(cs)) { return "Radius"; } if (Object::cast_to<GPUParticlesCollisionBox3D>(cs) || Object::cast_to<GPUParticlesAttractorBox3D>(cs) || Object::cast_to<GPUParticlesAttractorVectorField3D>(cs) || Object::cast_to<GPUParticlesCollisionSDF3D>(cs) || Object::cast_to<GPUParticlesCollisionHeightField3D>(cs)) { - return "Extents"; + return "Size"; } return ""; } -Variant GPUParticlesCollision3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - const Node3D *cs = p_gizmo->get_spatial_node(); +Variant GPUParticlesCollision3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + const Node3D *cs = p_gizmo->get_node_3d(); if (Object::cast_to<GPUParticlesCollisionSphere3D>(cs) || Object::cast_to<GPUParticlesAttractorSphere3D>(cs)) { - return p_gizmo->get_spatial_node()->call("get_radius"); + return p_gizmo->get_node_3d()->call("get_radius"); } if (Object::cast_to<GPUParticlesCollisionBox3D>(cs) || Object::cast_to<GPUParticlesAttractorBox3D>(cs) || Object::cast_to<GPUParticlesAttractorVectorField3D>(cs) || Object::cast_to<GPUParticlesCollisionSDF3D>(cs) || Object::cast_to<GPUParticlesCollisionHeightField3D>(cs)) { - return Vector3(p_gizmo->get_spatial_node()->call("get_extents")); + return Vector3(p_gizmo->get_node_3d()->call("get_size")); } return Variant(); } -void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - Node3D *sn = p_gizmo->get_spatial_node(); +void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + Node3D *sn = p_gizmo->get_node_3d(); Transform3D gt = sn->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -2823,7 +3235,7 @@ void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_g axis[p_id] = 1.0; Vector3 ra, rb; Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); - float d = ra[p_id]; + float d = ra[p_id] * 2; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } @@ -2832,14 +3244,14 @@ void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_g d = 0.001; } - Vector3 he = sn->call("get_extents"); + Vector3 he = sn->call("get_size"); he[p_id] = d; - sn->call("set_extents", he); + sn->call("set_size", he); } } -void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - Node3D *sn = p_gizmo->get_spatial_node(); +void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + Node3D *sn = p_gizmo->get_node_3d(); if (Object::cast_to<GPUParticlesCollisionSphere3D>(sn) || Object::cast_to<GPUParticlesAttractorSphere3D>(sn)) { if (p_cancel) { @@ -2847,7 +3259,7 @@ void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo * return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change Radius")); ur->add_do_method(sn, "set_radius", sn->call("get_radius")); ur->add_undo_method(sn, "set_radius", p_restore); @@ -2856,29 +3268,34 @@ void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo * if (Object::cast_to<GPUParticlesCollisionBox3D>(sn) || Object::cast_to<GPUParticlesAttractorBox3D>(sn) || Object::cast_to<GPUParticlesAttractorVectorField3D>(sn) || Object::cast_to<GPUParticlesCollisionSDF3D>(sn) || Object::cast_to<GPUParticlesCollisionHeightField3D>(sn)) { if (p_cancel) { - sn->call("set_extents", p_restore); + sn->call("set_size", p_restore); return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Change Box Shape Extents")); - ur->add_do_method(sn, "set_extents", sn->call("get_extents")); - ur->add_undo_method(sn, "set_extents", p_restore); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change Box Shape Size")); + ur->add_do_method(sn, "set_size", sn->call("get_size")); + ur->add_undo_method(sn, "set_size", p_restore); ur->commit_action(); } } void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Node3D *cs = p_gizmo->get_spatial_node(); + Node3D *cs = p_gizmo->get_node_3d(); p_gizmo->clear(); - const Ref<Material> material = - get_material("shape_material", p_gizmo); - const Ref<Material> material_internal = - get_material("shape_material_internal", p_gizmo); + Ref<Material> material; + Ref<Material> material_internal; + if (Object::cast_to<GPUParticlesAttractor3D>(cs)) { + material = get_material("shape_material_attractor", p_gizmo); + material_internal = get_material("shape_material_attractor_internal", p_gizmo); + } else { + material = get_material("shape_material_collision", p_gizmo); + material_internal = get_material("shape_material_collision_internal", p_gizmo); + } - Ref<Material> handles_material = get_material("handles"); + const Ref<Material> handles_material = get_material("handles"); if (Object::cast_to<GPUParticlesCollisionSphere3D>(cs) || Object::cast_to<GPUParticlesAttractorSphere3D>(cs)) { float r = cs->call("get_radius"); @@ -2886,8 +3303,8 @@ void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Vector<Vector3> points; for (int i = 0; i <= 360; i++) { - float ra = Math::deg2rad((float)i); - float rb = Math::deg2rad((float)i + 1); + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 1); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; @@ -2925,8 +3342,8 @@ void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (Object::cast_to<GPUParticlesCollisionBox3D>(cs) || Object::cast_to<GPUParticlesAttractorBox3D>(cs) || Object::cast_to<GPUParticlesAttractorVectorField3D>(cs) || Object::cast_to<GPUParticlesCollisionSDF3D>(cs) || Object::cast_to<GPUParticlesCollisionHeightField3D>(cs)) { Vector<Vector3> lines; AABB aabb; - aabb.position = -cs->call("get_extents").operator Vector3(); - aabb.size = aabb.position * -2; + aabb.size = cs->call("get_size").operator Vector3(); + aabb.position = aabb.size / -2; for (int i = 0; i < 12; i++) { Vector3 a, b; @@ -2939,7 +3356,7 @@ void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { for (int i = 0; i < 3; i++) { Vector3 ax; - ax[i] = cs->call("get_extents").operator Vector3()[i]; + ax[i] = cs->call("get_size").operator Vector3()[i] / 2; handles.push_back(ax); } @@ -2961,13 +3378,8 @@ void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { continue; } - Vector2 dir; - dir[j] = 1.0; - Vector2 ta, tb; int j_n1 = (j + 1) % 3; int j_n2 = (j + 2) % 3; - ta[j_n1] = 1.0; - tb[j_n2] = 1.0; for (int k = 0; k < 4; k++) { Vector3 from = aabb.position, to = aabb.position; @@ -3027,14 +3439,14 @@ int ReflectionProbeGizmoPlugin::get_priority() const { return -1; } -String ReflectionProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String ReflectionProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { switch (p_id) { case 0: - return "Extents X"; + return "Size X"; case 1: - return "Extents Y"; + return "Size Y"; case 2: - return "Extents Z"; + return "Size Z"; case 3: return "Origin X"; case 4: @@ -3046,19 +3458,19 @@ String ReflectionProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gi return ""; } -Variant ReflectionProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node()); - return AABB(probe->get_extents(), probe->get_origin_offset()); +Variant ReflectionProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_node_3d()); + return AABB(probe->get_origin_offset(), probe->get_size()); } -void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node()); +void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_node_3d()); Transform3D gt = probe->get_global_transform(); Transform3D gi = gt.affine_inverse(); if (p_id < 3) { - Vector3 extents = probe->get_extents(); + Vector3 size = probe->get_size(); Vector3 ray_from = p_camera->project_ray_origin(p_point); Vector3 ray_dir = p_camera->project_ray_normal(p_point); @@ -3070,7 +3482,7 @@ void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, in Vector3 ra, rb; Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb); - float d = ra[p_id]; + float d = ra[p_id] * 2; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } @@ -3079,8 +3491,8 @@ void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, in d = 0.001; } - extents[p_id] = d; - probe->set_extents(extents); + size[p_id] = d; + probe->set_size(size); } else { p_id -= 3; @@ -3108,38 +3520,38 @@ void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, in } } -void ReflectionProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node()); +void ReflectionProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_node_3d()); AABB restore = p_restore; if (p_cancel) { - probe->set_extents(restore.position); - probe->set_origin_offset(restore.size); + probe->set_origin_offset(restore.position); + probe->set_size(restore.size); return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Change Probe Extents")); - ur->add_do_method(probe, "set_extents", probe->get_extents()); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change Probe Size")); + ur->add_do_method(probe, "set_size", probe->get_size()); ur->add_do_method(probe, "set_origin_offset", probe->get_origin_offset()); - ur->add_undo_method(probe, "set_extents", restore.position); - ur->add_undo_method(probe, "set_origin_offset", restore.size); + ur->add_undo_method(probe, "set_size", restore.size); + ur->add_undo_method(probe, "set_origin_offset", restore.position); ur->commit_action(); } void ReflectionProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node()); + ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_node_3d()); p_gizmo->clear(); Vector<Vector3> lines; Vector<Vector3> internal_lines; - Vector3 extents = probe->get_extents(); + Vector3 size = probe->get_size(); AABB aabb; - aabb.position = -extents; - aabb.size = extents * 2; + aabb.position = -size / 2; + aabb.size = size; for (int i = 0; i < 12; i++) { Vector3 a, b; @@ -3181,7 +3593,7 @@ void ReflectionProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (p_gizmo->is_selected()) { Ref<Material> solid_material = get_material("reflection_probe_solid_material", p_gizmo); - p_gizmo->add_solid_box(solid_material, probe->get_extents() * 2.0); + p_gizmo->add_solid_box(solid_material, probe->get_size()); } p_gizmo->add_unscaled_billboard(icon, 0.05); @@ -3212,31 +3624,31 @@ int DecalGizmoPlugin::get_priority() const { return -1; } -String DecalGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String DecalGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { switch (p_id) { case 0: - return "Extents X"; + return "Size X"; case 1: - return "Extents Y"; + return "Size Y"; case 2: - return "Extents Z"; + return "Size Z"; } return ""; } -Variant DecalGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node()); - return decal->get_extents(); +Variant DecalGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d()); + return decal->get_size(); } -void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node()); +void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d()); Transform3D gt = decal->get_global_transform(); Transform3D gi = gt.affine_inverse(); - Vector3 extents = decal->get_extents(); + Vector3 size = decal->get_size(); Vector3 ray_from = p_camera->project_ray_origin(p_point); Vector3 ray_dir = p_camera->project_ray_normal(p_point); @@ -3248,7 +3660,7 @@ void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Ca Vector3 ra, rb; Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb); - float d = ra[p_id]; + float d = ra[p_id] * 2; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } @@ -3257,38 +3669,38 @@ void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Ca d = 0.001; } - extents[p_id] = d; - decal->set_extents(extents); + size[p_id] = d; + decal->set_size(size); } -void DecalGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node()); +void DecalGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d()); Vector3 restore = p_restore; if (p_cancel) { - decal->set_extents(restore); + decal->set_size(restore); return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Change Decal Extents")); - ur->add_do_method(decal, "set_extents", decal->get_extents()); - ur->add_undo_method(decal, "set_extents", restore); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change Decal Size")); + ur->add_do_method(decal, "set_size", decal->get_size()); + ur->add_undo_method(decal, "set_size", restore); ur->commit_action(); } void DecalGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node()); + Decal *decal = Object::cast_to<Decal>(p_gizmo->get_node_3d()); p_gizmo->clear(); Vector<Vector3> lines; - Vector3 extents = decal->get_extents(); + Vector3 size = decal->get_size(); AABB aabb; - aabb.position = -extents; - aabb.size = extents * 2; + aabb.position = -size / 2; + aabb.size = size; for (int i = 0; i < 12; i++) { Vector3 a, b; @@ -3306,8 +3718,9 @@ void DecalGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } } - lines.push_back(Vector3(0, extents.y, 0)); - lines.push_back(Vector3(0, extents.y * 1.2, 0)); + float half_size_y = size.y / 2; + lines.push_back(Vector3(0, half_size_y, 0)); + lines.push_back(Vector3(0, half_size_y * 1.2, 0)); Vector<Vector3> handles; @@ -3352,31 +3765,31 @@ int VoxelGIGizmoPlugin::get_priority() const { return -1; } -String VoxelGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String VoxelGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { switch (p_id) { case 0: - return "Extents X"; + return "Size X"; case 1: - return "Extents Y"; + return "Size Y"; case 2: - return "Extents Z"; + return "Size Z"; } return ""; } -Variant VoxelGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node()); - return probe->get_extents(); +Variant VoxelGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_node_3d()); + return probe->get_size(); } -void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node()); +void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_node_3d()); Transform3D gt = probe->get_global_transform(); Transform3D gi = gt.affine_inverse(); - Vector3 extents = probe->get_extents(); + Vector3 size = probe->get_size(); Vector3 ray_from = p_camera->project_ray_origin(p_point); Vector3 ray_dir = p_camera->project_ray_normal(p_point); @@ -3388,7 +3801,7 @@ void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Vector3 ra, rb; Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb); - float d = ra[p_id]; + float d = ra[p_id] * 2; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } @@ -3397,29 +3810,29 @@ void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, d = 0.001; } - extents[p_id] = d; - probe->set_extents(extents); + size[p_id] = d; + probe->set_size(size); } -void VoxelGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node()); +void VoxelGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_node_3d()); Vector3 restore = p_restore; if (p_cancel) { - probe->set_extents(restore); + probe->set_size(restore); return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Change Probe Extents")); - ur->add_do_method(probe, "set_extents", probe->get_extents()); - ur->add_undo_method(probe, "set_extents", restore); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change Probe Size")); + ur->add_do_method(probe, "set_size", probe->get_size()); + ur->add_undo_method(probe, "set_size", restore); ur->commit_action(); } void VoxelGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node()); + VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_node_3d()); Ref<Material> material = get_material("voxel_gi_material", p_gizmo); Ref<Material> icon = get_material("voxel_gi_icon", p_gizmo); @@ -3428,11 +3841,11 @@ void VoxelGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->clear(); Vector<Vector3> lines; - Vector3 extents = probe->get_extents(); + Vector3 size = probe->get_size(); static const int subdivs[VoxelGI::SUBDIV_MAX] = { 64, 128, 256, 512 }; - AABB aabb = AABB(-extents, extents * 2); + AABB aabb = AABB(-size / 2, size); int subdiv = subdivs[probe->get_subdiv()]; float cell_size = aabb.get_longest_axis_size() / subdiv; @@ -3453,13 +3866,8 @@ void VoxelGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { continue; } - Vector2 dir; - dir[j] = 1.0; - Vector2 ta, tb; int j_n1 = (j + 1) % 3; int j_n2 = (j + 2) % 3; - ta[j_n1] = 1.0; - tb[j_n2] = 1.0; for (int k = 0; k < 4; k++) { Vector3 from = aabb.position, to = aabb.position; @@ -3521,20 +3929,6 @@ LightmapGIGizmoPlugin::LightmapGIGizmoPlugin() { create_icon_material("baked_indirect_light_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoLightmapGI"), SNAME("EditorIcons"))); } -String LightmapGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { - return ""; -} - -Variant LightmapGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - return Variant(); -} - -void LightmapGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { -} - -void LightmapGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { -} - bool LightmapGIGizmoPlugin::has_gizmo(Node3D *p_spatial) { return Object::cast_to<LightmapGI>(p_spatial) != nullptr; } @@ -3549,7 +3943,7 @@ int LightmapGIGizmoPlugin::get_priority() const { void LightmapGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Ref<Material> icon = get_material("baked_indirect_light_icon", p_gizmo); - LightmapGI *baker = Object::cast_to<LightmapGI>(p_gizmo->get_spatial_node()); + LightmapGI *baker = Object::cast_to<LightmapGI>(p_gizmo->get_node_3d()); Ref<LightmapGIData> data = baker->get_light_data(); p_gizmo->add_unscaled_billboard(icon, 0.05); @@ -3564,7 +3958,7 @@ void LightmapGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->clear(); Vector<Vector3> lines; - Set<Vector2i> lines_found; + HashSet<Vector2i> lines_found; Vector<Vector3> points = data->get_capture_points(); if (points.size() == 0) { @@ -3703,20 +4097,6 @@ LightmapProbeGizmoPlugin::LightmapProbeGizmoPlugin() { create_material("lightprobe_lines", gizmo_color); } -String LightmapProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { - return ""; -} - -Variant LightmapProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - return Variant(); -} - -void LightmapProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { -} - -void LightmapProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { -} - bool LightmapProbeGizmoPlugin::has_gizmo(Node3D *p_spatial) { return Object::cast_to<LightmapProbe>(p_spatial) != nullptr; } @@ -3793,7 +4173,7 @@ void LightmapProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { //// CollisionObject3DGizmoPlugin::CollisionObject3DGizmoPlugin() { - const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + const Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); const float gizmo_value = gizmo_color.get_v(); const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); @@ -3813,13 +4193,13 @@ int CollisionObject3DGizmoPlugin::get_priority() const { } void CollisionObject3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_gizmo->get_spatial_node()); + CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); - List<uint32_t> owners; - co->get_shape_owners(&owners); - for (uint32_t &owner_id : owners) { + List<uint32_t> owner_ids; + co->get_shape_owners(&owner_ids); + for (uint32_t &owner_id : owner_ids) { Transform3D xform = co->shape_owner_get_transform(owner_id); Object *owner = co->shape_owner_get_owner(owner_id); // Exclude CollisionShape3D and CollisionPolygon3D as they have their gizmo. @@ -3843,7 +4223,7 @@ void CollisionObject3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { //// CollisionShape3DGizmoPlugin::CollisionShape3DGizmoPlugin() { - const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + const Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); const float gizmo_value = gizmo_color.get_v(); const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); @@ -3863,8 +4243,8 @@ int CollisionShape3DGizmoPlugin::get_priority() const { return -1; } -String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { - const CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); +String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + const CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d()); Ref<Shape3D> s = cs->get_shape(); if (s.is_null()) { @@ -3894,8 +4274,8 @@ String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_g return ""; } -Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); +Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d()); Ref<Shape3D> s = cs->get_shape(); if (s.is_null()) { @@ -3930,8 +4310,8 @@ Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p return Variant(); } -void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); +void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d()); Ref<Shape3D> s = cs->get_shape(); if (s.is_null()) { @@ -3984,7 +4364,7 @@ void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i Ref<BoxShape3D> bs = s; Vector3 ra, rb; Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); - float d = ra[p_id]; + float d = ra[p_id] * 2; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } @@ -3994,7 +4374,7 @@ void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i } Vector3 he = bs->get_size(); - he[p_id] = d * 2; + he[p_id] = d; bs->set_size(he); } @@ -4044,8 +4424,8 @@ void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i } } -void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); +void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d()); Ref<Shape3D> s = cs->get_shape(); if (s.is_null()) { @@ -4059,7 +4439,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change Sphere Shape Radius")); ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius()); ur->add_undo_method(ss.ptr(), "set_radius", p_restore); @@ -4073,7 +4453,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change Box Shape Size")); ur->add_do_method(ss.ptr(), "set_size", ss->get_size()); ur->add_undo_method(ss.ptr(), "set_size", p_restore); @@ -4090,7 +4470,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); if (p_id == 0) { ur->create_action(TTR("Change Capsule Shape Radius")); ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius()); @@ -4115,7 +4495,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); if (p_id == 0) { ur->create_action(TTR("Change Cylinder Shape Radius")); ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius()); @@ -4140,7 +4520,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Change Separation Ray Shape Length")); ur->add_do_method(ss.ptr(), "set_length", ss->get_length()); ur->add_undo_method(ss.ptr(), "set_length", p_restore); @@ -4149,7 +4529,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo } void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); + CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -4169,8 +4549,8 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Vector<Vector3> points; for (int i = 0; i <= 360; i++) { - float ra = Math::deg2rad((float)i); - float rb = Math::deg2rad((float)i + 1); + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 1); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; @@ -4241,8 +4621,8 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Vector3 d(0, height * 0.5 - radius, 0); for (int i = 0; i < 360; i++) { - float ra = Math::deg2rad((float)i); - float rb = Math::deg2rad((float)i + 1); + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 1); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius; @@ -4296,9 +4676,10 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_collision_segments(collision_segments); - Vector<Vector3> handles; - handles.push_back(Vector3(cs2->get_radius(), 0, 0)); - handles.push_back(Vector3(0, cs2->get_height() * 0.5, 0)); + Vector<Vector3> handles = { + Vector3(cs2->get_radius(), 0, 0), + Vector3(0, cs2->get_height() * 0.5, 0) + }; p_gizmo->add_handles(handles, handles_material); } @@ -4311,8 +4692,8 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Vector3 d(0, height * 0.5, 0); for (int i = 0; i < 360; i++) { - float ra = Math::deg2rad((float)i); - float rb = Math::deg2rad((float)i + 1); + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 1); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius; @@ -4352,16 +4733,16 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_collision_segments(collision_segments); - Vector<Vector3> handles; - handles.push_back(Vector3(cs2->get_radius(), 0, 0)); - handles.push_back(Vector3(0, cs2->get_height() * 0.5, 0)); + Vector<Vector3> handles = { + Vector3(cs2->get_radius(), 0, 0), + Vector3(0, cs2->get_height() * 0.5, 0) + }; p_gizmo->add_handles(handles, handles_material); } if (Object::cast_to<WorldBoundaryShape3D>(*s)) { Ref<WorldBoundaryShape3D> wbs = s; const Plane &p = wbs->get_plane(); - Vector<Vector3> points; Vector3 n1 = p.get_any_perpendicular_normal(); Vector3 n2 = p.normal.cross(n1).normalized(); @@ -4373,16 +4754,18 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p.normal * p.d + n1 * -10.0 + n2 * 10.0, }; - points.push_back(pface[0]); - points.push_back(pface[1]); - points.push_back(pface[1]); - points.push_back(pface[2]); - points.push_back(pface[2]); - points.push_back(pface[3]); - points.push_back(pface[3]); - points.push_back(pface[0]); - points.push_back(p.normal * p.d); - points.push_back(p.normal * p.d + p.normal * 3); + Vector<Vector3> points = { + pface[0], + pface[1], + pface[1], + pface[2], + pface[2], + pface[3], + pface[3], + pface[0], + p.normal * p.d, + p.normal * p.d + p.normal * 3 + }; p_gizmo->add_lines(points, material); p_gizmo->add_collision_segments(points); @@ -4398,9 +4781,9 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (err == OK) { Vector<Vector3> points2; points2.resize(md.edges.size() * 2); - for (int i = 0; i < md.edges.size(); i++) { - points2.write[i * 2 + 0] = md.vertices[md.edges[i].a]; - points2.write[i * 2 + 1] = md.vertices[md.edges[i].b]; + for (uint32_t i = 0; i < md.edges.size(); i++) { + points2.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a]; + points2.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b]; } p_gizmo->add_lines(points2, material); @@ -4419,9 +4802,10 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (Object::cast_to<SeparationRayShape3D>(*s)) { Ref<SeparationRayShape3D> rs = s; - Vector<Vector3> points; - points.push_back(Vector3()); - points.push_back(Vector3(0, 0, rs->get_length())); + Vector<Vector3> points = { + Vector3(), + Vector3(0, 0, rs->get_length()) + }; p_gizmo->add_lines(points, material); p_gizmo->add_collision_segments(points); Vector<Vector3> handles; @@ -4440,7 +4824,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ///// CollisionPolygon3DGizmoPlugin::CollisionPolygon3DGizmoPlugin() { - const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + const Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); const float gizmo_value = gizmo_color.get_v(); const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); @@ -4460,7 +4844,7 @@ int CollisionPolygon3DGizmoPlugin::get_priority() const { } void CollisionPolygon3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - CollisionPolygon3D *polygon = Object::cast_to<CollisionPolygon3D>(p_gizmo->get_spatial_node()); + CollisionPolygon3D *polygon = Object::cast_to<CollisionPolygon3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -4488,10 +4872,14 @@ void CollisionPolygon3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { //// NavigationRegion3DGizmoPlugin::NavigationRegion3DGizmoPlugin() { - create_material("navigation_edge_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_edge", Color(0.5, 1, 1))); - create_material("navigation_edge_material_disabled", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_edge_disabled", Color(0.7, 0.7, 0.7))); - create_material("navigation_solid_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_solid", Color(0.5, 1, 1, 0.4))); - create_material("navigation_solid_material_disabled", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_solid_disabled", Color(0.7, 0.7, 0.7, 0.4))); + create_material("face_material", NavigationServer3D::get_singleton()->get_debug_navigation_geometry_face_color(), false, false, true); + create_material("face_material_disabled", NavigationServer3D::get_singleton()->get_debug_navigation_geometry_face_disabled_color(), false, false, true); + create_material("edge_material", NavigationServer3D::get_singleton()->get_debug_navigation_geometry_edge_color()); + create_material("edge_material_disabled", NavigationServer3D::get_singleton()->get_debug_navigation_geometry_edge_disabled_color()); + + Color baking_aabb_material_color = Color(0.8, 0.5, 0.7); + baking_aabb_material_color.a = 0.1; + create_material("baking_aabb_material", baking_aabb_material_color); } bool NavigationRegion3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { @@ -4507,24 +4895,29 @@ int NavigationRegion3DGizmoPlugin::get_priority() const { } void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - NavigationRegion3D *navmesh = Object::cast_to<NavigationRegion3D>(p_gizmo->get_spatial_node()); - - Ref<Material> edge_material = get_material("navigation_edge_material", p_gizmo); - Ref<Material> edge_material_disabled = get_material("navigation_edge_material_disabled", p_gizmo); - Ref<Material> solid_material = get_material("navigation_solid_material", p_gizmo); - Ref<Material> solid_material_disabled = get_material("navigation_solid_material_disabled", p_gizmo); + NavigationRegion3D *navigationregion = Object::cast_to<NavigationRegion3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); - Ref<NavigationMesh> navmeshie = navmesh->get_navigation_mesh(); - if (navmeshie.is_null()) { + Ref<NavigationMesh> navigationmesh = navigationregion->get_navigation_mesh(); + if (navigationmesh.is_null()) { return; } - Vector<Vector3> vertices = navmeshie->get_vertices(); + AABB baking_aabb = navigationmesh->get_filter_baking_aabb(); + if (baking_aabb.has_volume()) { + Vector3 baking_aabb_offset = navigationmesh->get_filter_baking_aabb_offset(); + + if (p_gizmo->is_selected()) { + Ref<Material> material = get_material("baking_aabb_material", p_gizmo); + p_gizmo->add_solid_box(material, baking_aabb.get_size(), baking_aabb.get_center() + baking_aabb_offset); + } + } + + Vector<Vector3> vertices = navigationmesh->get_vertices(); const Vector3 *vr = vertices.ptr(); List<Face3> faces; - for (int i = 0; i < navmeshie->get_polygon_count(); i++) { - Vector<int> p = navmeshie->get_polygon(i); + for (int i = 0; i < navigationmesh->get_polygon_count(); i++) { + Vector<int> p = navigationmesh->get_polygon(i); for (int j = 2; j < p.size(); j++) { Face3 f; @@ -4540,7 +4933,7 @@ void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { return; } - Map<_EdgeKey, bool> edge_map; + HashMap<_EdgeKey, bool, _EdgeKey> edge_map; Vector<Vector3> tmeshfaces; tmeshfaces.resize(faces.size() * 3); @@ -4558,10 +4951,10 @@ void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { SWAP(ek.from, ek.to); } - Map<_EdgeKey, bool>::Element *F = edge_map.find(ek); + HashMap<_EdgeKey, bool, _EdgeKey>::Iterator F = edge_map.find(ek); if (F) { - F->get() = false; + F->value = false; } else { edge_map[ek] = true; @@ -4581,18 +4974,243 @@ void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Ref<TriangleMesh> tmesh = memnew(TriangleMesh); tmesh->create(tmeshfaces); - if (lines.size()) { - p_gizmo->add_lines(lines, navmesh->is_enabled() ? edge_material : edge_material_disabled); - } p_gizmo->add_collision_triangles(tmesh); - Ref<ArrayMesh> m = memnew(ArrayMesh); - Array a; - a.resize(Mesh::ARRAY_MAX); - a[0] = tmeshfaces; - m->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a); - m->surface_set_material(0, navmesh->is_enabled() ? solid_material : solid_material_disabled); - p_gizmo->add_mesh(m); p_gizmo->add_collision_segments(lines); + + Ref<ArrayMesh> debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh)); + int polygon_count = navigationmesh->get_polygon_count(); + + // build geometry face surface + Vector<Vector3> face_vertex_array; + face_vertex_array.resize(polygon_count * 3); + + for (int i = 0; i < polygon_count; i++) { + Vector<int> polygon = navigationmesh->get_polygon(i); + + face_vertex_array.push_back(vertices[polygon[0]]); + face_vertex_array.push_back(vertices[polygon[1]]); + face_vertex_array.push_back(vertices[polygon[2]]); + } + + Array face_mesh_array; + face_mesh_array.resize(Mesh::ARRAY_MAX); + face_mesh_array[Mesh::ARRAY_VERTEX] = face_vertex_array; + + // if enabled add vertex colors to colorize each face individually + RandomPCG rand; + bool enabled_geometry_face_random_color = NavigationServer3D::get_singleton()->get_debug_navigation_enable_geometry_face_random_color(); + if (enabled_geometry_face_random_color) { + Color debug_navigation_geometry_face_color = NavigationServer3D::get_singleton()->get_debug_navigation_geometry_face_color(); + Color polygon_color = debug_navigation_geometry_face_color; + + Vector<Color> face_color_array; + face_color_array.resize(polygon_count * 3); + + for (int i = 0; i < polygon_count; i++) { + // Generate the polygon color, slightly randomly modified from the settings one. + polygon_color.set_hsv(debug_navigation_geometry_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1, debug_navigation_geometry_face_color.get_s(), debug_navigation_geometry_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2); + polygon_color.a = debug_navigation_geometry_face_color.a; + + Vector<int> polygon = navigationmesh->get_polygon(i); + + face_color_array.push_back(polygon_color); + face_color_array.push_back(polygon_color); + face_color_array.push_back(polygon_color); + } + face_mesh_array[Mesh::ARRAY_COLOR] = face_color_array; + } + + debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, face_mesh_array); + p_gizmo->add_mesh(debug_mesh, navigationregion->is_enabled() ? get_material("face_material", p_gizmo) : get_material("face_material_disabled", p_gizmo)); + + // if enabled build geometry edge line surface + bool enabled_edge_lines = NavigationServer3D::get_singleton()->get_debug_navigation_enable_edge_lines(); + if (enabled_edge_lines) { + Vector<Vector3> line_vertex_array; + line_vertex_array.resize(polygon_count * 6); + + for (int i = 0; i < polygon_count; i++) { + Vector<int> polygon = navigationmesh->get_polygon(i); + + line_vertex_array.push_back(vertices[polygon[0]]); + line_vertex_array.push_back(vertices[polygon[1]]); + line_vertex_array.push_back(vertices[polygon[1]]); + line_vertex_array.push_back(vertices[polygon[2]]); + line_vertex_array.push_back(vertices[polygon[2]]); + line_vertex_array.push_back(vertices[polygon[0]]); + } + + p_gizmo->add_lines(line_vertex_array, navigationregion->is_enabled() ? get_material("edge_material", p_gizmo) : get_material("edge_material_disabled", p_gizmo)); + } +} + +//// + +NavigationLink3DGizmoPlugin::NavigationLink3DGizmoPlugin() { + create_material("navigation_link_material", NavigationServer3D::get_singleton()->get_debug_navigation_link_connection_color()); + create_material("navigation_link_material_disabled", NavigationServer3D::get_singleton()->get_debug_navigation_link_connection_disabled_color()); + create_handle_material("handles"); +} + +bool NavigationLink3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<NavigationLink3D>(p_spatial) != nullptr; +} + +String NavigationLink3DGizmoPlugin::get_gizmo_name() const { + return "NavigationLink3D"; +} + +int NavigationLink3DGizmoPlugin::get_priority() const { + return -1; +} + +void NavigationLink3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_node_3d()); + + RID nav_map = link->get_world_3d()->get_navigation_map(); + real_t search_radius = NavigationServer3D::get_singleton()->map_get_link_connection_radius(nav_map); + Vector3 up_vector = NavigationServer3D::get_singleton()->map_get_up(nav_map); + Vector3::Axis up_axis = up_vector.max_axis_index(); + + Vector3 start_position = link->get_start_position(); + Vector3 end_position = link->get_end_position(); + + Ref<Material> link_material = get_material("navigation_link_material", p_gizmo); + Ref<Material> link_material_disabled = get_material("navigation_link_material_disabled", p_gizmo); + Ref<Material> handles_material = get_material("handles"); + + p_gizmo->clear(); + + // Draw line between the points. + Vector<Vector3> lines; + lines.append(start_position); + lines.append(end_position); + + // Draw start position search radius + for (int i = 0; i < 30; i++) { + // Create a circle + const float ra = Math::deg_to_rad((float)(i * 12)); + const float rb = Math::deg_to_rad((float)((i + 1) * 12)); + const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius; + const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius; + + // Draw axis-aligned circle + switch (up_axis) { + case Vector3::AXIS_X: + lines.append(start_position + Vector3(0, a.x, a.y)); + lines.append(start_position + Vector3(0, b.x, b.y)); + break; + case Vector3::AXIS_Y: + lines.append(start_position + Vector3(a.x, 0, a.y)); + lines.append(start_position + Vector3(b.x, 0, b.y)); + break; + case Vector3::AXIS_Z: + lines.append(start_position + Vector3(a.x, a.y, 0)); + lines.append(start_position + Vector3(b.x, b.y, 0)); + break; + } + } + + // Draw end position search radius + for (int i = 0; i < 30; i++) { + // Create a circle + const float ra = Math::deg_to_rad((float)(i * 12)); + const float rb = Math::deg_to_rad((float)((i + 1) * 12)); + const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius; + const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius; + + // Draw axis-aligned circle + switch (up_axis) { + case Vector3::AXIS_X: + lines.append(end_position + Vector3(0, a.x, a.y)); + lines.append(end_position + Vector3(0, b.x, b.y)); + break; + case Vector3::AXIS_Y: + lines.append(end_position + Vector3(a.x, 0, a.y)); + lines.append(end_position + Vector3(b.x, 0, b.y)); + break; + case Vector3::AXIS_Z: + lines.append(end_position + Vector3(a.x, a.y, 0)); + lines.append(end_position + Vector3(b.x, b.y, 0)); + break; + } + } + + p_gizmo->add_lines(lines, link->is_enabled() ? link_material : link_material_disabled); + p_gizmo->add_collision_segments(lines); + + Vector<Vector3> handles; + handles.append(start_position); + handles.append(end_position); + p_gizmo->add_handles(handles, handles_material); +} + +String NavigationLink3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + return p_id == 0 ? TTR("Start Location") : TTR("End Location"); +} + +Variant NavigationLink3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_node_3d()); + return p_id == 0 ? link->get_start_position() : link->get_end_position(); +} + +void NavigationLink3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_node_3d()); + + Transform3D gt = link->get_global_transform(); + Transform3D gi = gt.affine_inverse(); + + Transform3D ct = p_camera->get_global_transform(); + Vector3 cam_dir = ct.basis.get_column(Vector3::AXIS_Z); + + Vector3 ray_from = p_camera->project_ray_origin(p_point); + Vector3 ray_dir = p_camera->project_ray_normal(p_point); + + Vector3 position = p_id == 0 ? link->get_start_position() : link->get_end_position(); + Plane move_plane = Plane(cam_dir, gt.xform(position)); + + Vector3 intersection; + if (!move_plane.intersects_ray(ray_from, ray_dir, &intersection)) { + return; + } + + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + double snap = Node3DEditor::get_singleton()->get_translate_snap(); + intersection.snap(Vector3(snap, snap, snap)); + } + + position = gi.xform(intersection); + if (p_id == 0) { + link->set_start_position(position); + } else if (p_id == 1) { + link->set_end_position(position); + } +} + +void NavigationLink3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_node_3d()); + + if (p_cancel) { + if (p_id == 0) { + link->set_start_position(p_restore); + } else { + link->set_end_position(p_restore); + } + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + if (p_id == 0) { + ur->create_action(TTR("Change Start Position")); + ur->add_do_method(link, "set_start_position", link->get_start_position()); + ur->add_undo_method(link, "set_start_position", p_restore); + } else { + ur->create_action(TTR("Change End Position")); + ur->add_do_method(link, "set_end_position", link->get_end_position()); + ur->add_undo_method(link, "set_end_position", p_restore); + } + + ur->commit_action(); } ////// @@ -4617,7 +5235,7 @@ Basis JointGizmosDrawer::look_body(const Transform3D &p_joint_transform, const T v_y.normalize(); Basis base; - base.set(v_x, v_y, v_z); + base.set_columns(v_x, v_y, v_z); // Absorb current joint transform base = p_joint_transform.basis.inverse() * base; @@ -4642,7 +5260,7 @@ Basis JointGizmosDrawer::look_body_toward_x(const Transform3D &p_joint_transform const Vector3 &p_eye(p_joint_transform.origin); const Vector3 &p_target(p_body_transform.origin); - const Vector3 p_front(p_joint_transform.basis.get_axis(0)); + const Vector3 p_front(p_joint_transform.basis.get_column(0)); Vector3 v_x, v_y, v_z; @@ -4661,7 +5279,7 @@ Basis JointGizmosDrawer::look_body_toward_x(const Transform3D &p_joint_transform v_x.normalize(); Basis base; - base.set(v_x, v_y, v_z); + base.set_columns(v_x, v_y, v_z); // Absorb current joint transform base = p_joint_transform.basis.inverse() * base; @@ -4673,7 +5291,7 @@ Basis JointGizmosDrawer::look_body_toward_y(const Transform3D &p_joint_transform const Vector3 &p_eye(p_joint_transform.origin); const Vector3 &p_target(p_body_transform.origin); - const Vector3 p_up(p_joint_transform.basis.get_axis(1)); + const Vector3 p_up(p_joint_transform.basis.get_column(1)); Vector3 v_x, v_y, v_z; @@ -4692,7 +5310,7 @@ Basis JointGizmosDrawer::look_body_toward_y(const Transform3D &p_joint_transform v_y.normalize(); Basis base; - base.set(v_x, v_y, v_z); + base.set_columns(v_x, v_y, v_z); // Absorb current joint transform base = p_joint_transform.basis.inverse() * base; @@ -4704,7 +5322,7 @@ Basis JointGizmosDrawer::look_body_toward_z(const Transform3D &p_joint_transform const Vector3 &p_eye(p_joint_transform.origin); const Vector3 &p_target(p_body_transform.origin); - const Vector3 p_lateral(p_joint_transform.basis.get_axis(2)); + const Vector3 p_lateral(p_joint_transform.basis.get_column(2)); Vector3 v_x, v_y, v_z; @@ -4723,7 +5341,7 @@ Basis JointGizmosDrawer::look_body_toward_z(const Transform3D &p_joint_transform v_x.normalize(); Basis base; - base.set(v_x, v_y, v_z); + base.set_columns(v_x, v_y, v_z); // Absorb current joint transform base = p_joint_transform.basis.inverse() * base; @@ -4733,8 +5351,8 @@ Basis JointGizmosDrawer::look_body_toward_z(const Transform3D &p_joint_transform void JointGizmosDrawer::draw_circle(Vector3::Axis p_axis, real_t p_radius, const Transform3D &p_offset, const Basis &p_base, real_t p_limit_lower, real_t p_limit_upper, Vector<Vector3> &r_points, bool p_inverse) { if (p_limit_lower == p_limit_upper) { - r_points.push_back(p_offset.translated(Vector3()).origin); - r_points.push_back(p_offset.translated(p_base.xform(Vector3(0.5, 0, 0))).origin); + r_points.push_back(p_offset.translated_local(Vector3()).origin); + r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(0.5, 0, 0))).origin); } else { if (p_limit_lower > p_limit_upper) { @@ -4776,20 +5394,20 @@ void JointGizmosDrawer::draw_circle(Vector3::Axis p_axis, real_t p_radius, const } if (i == points - 1) { - r_points.push_back(p_offset.translated(to).origin); - r_points.push_back(p_offset.translated(Vector3()).origin); + r_points.push_back(p_offset.translated_local(to).origin); + r_points.push_back(p_offset.translated_local(Vector3()).origin); } if (i == 0) { - r_points.push_back(p_offset.translated(from).origin); - r_points.push_back(p_offset.translated(Vector3()).origin); + r_points.push_back(p_offset.translated_local(from).origin); + r_points.push_back(p_offset.translated_local(Vector3()).origin); } - r_points.push_back(p_offset.translated(from).origin); - r_points.push_back(p_offset.translated(to).origin); + r_points.push_back(p_offset.translated_local(from).origin); + r_points.push_back(p_offset.translated_local(to).origin); } - r_points.push_back(p_offset.translated(Vector3(0, p_radius * 1.5, 0)).origin); - r_points.push_back(p_offset.translated(Vector3()).origin); + r_points.push_back(p_offset.translated_local(Vector3(0, p_radius * 1.5, 0)).origin); + r_points.push_back(p_offset.translated_local(Vector3()).origin); } } @@ -4800,44 +5418,44 @@ void JointGizmosDrawer::draw_cone(const Transform3D &p_offset, const Basis &p_ba //swing for (int i = 0; i < 360; i += 10) { - float ra = Math::deg2rad((float)i); - float rb = Math::deg2rad((float)i + 10); + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 10); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w; - r_points.push_back(p_offset.translated(p_base.xform(Vector3(d, a.x, a.y))).origin); - r_points.push_back(p_offset.translated(p_base.xform(Vector3(d, b.x, b.y))).origin); + r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(d, a.x, a.y))).origin); + r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(d, b.x, b.y))).origin); if (i % 90 == 0) { - r_points.push_back(p_offset.translated(p_base.xform(Vector3(d, a.x, a.y))).origin); - r_points.push_back(p_offset.translated(p_base.xform(Vector3())).origin); + r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(d, a.x, a.y))).origin); + r_points.push_back(p_offset.translated_local(p_base.xform(Vector3())).origin); } } - r_points.push_back(p_offset.translated(p_base.xform(Vector3())).origin); - r_points.push_back(p_offset.translated(p_base.xform(Vector3(1, 0, 0))).origin); + r_points.push_back(p_offset.translated_local(p_base.xform(Vector3())).origin); + r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(1, 0, 0))).origin); /// Twist - float ts = Math::rad2deg(p_twist); + float ts = Math::rad_to_deg(p_twist); ts = MIN(ts, 720); for (int i = 0; i < int(ts); i += 5) { - float ra = Math::deg2rad((float)i); - float rb = Math::deg2rad((float)i + 5); + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 5); float c = i / 720.0; float cn = (i + 5) / 720.0; Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w * c; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w * cn; - r_points.push_back(p_offset.translated(p_base.xform(Vector3(c, a.x, a.y))).origin); - r_points.push_back(p_offset.translated(p_base.xform(Vector3(cn, b.x, b.y))).origin); + r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(c, a.x, a.y))).origin); + r_points.push_back(p_offset.translated_local(p_base.xform(Vector3(cn, b.x, b.y))).origin); } } //// Joint3DGizmoPlugin::Joint3DGizmoPlugin() { - create_material("joint_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1))); + create_material("joint_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/joint")); create_material("joint_body_a_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint_body_a", Color(0.6, 0.8, 1))); create_material("joint_body_b_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint_body_b", Color(0.6, 0.9, 1))); @@ -4870,7 +5488,7 @@ int Joint3DGizmoPlugin::get_priority() const { } void Joint3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Joint3D *joint = Object::cast_to<Joint3D>(p_gizmo->get_spatial_node()); + Joint3D *joint = Object::cast_to<Joint3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); @@ -5014,17 +5632,17 @@ void Joint3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { void Joint3DGizmoPlugin::CreatePinJointGizmo(const Transform3D &p_offset, Vector<Vector3> &r_cursor_points) { float cs = 0.25; - r_cursor_points.push_back(p_offset.translated(Vector3(+cs, 0, 0)).origin); - r_cursor_points.push_back(p_offset.translated(Vector3(-cs, 0, 0)).origin); - r_cursor_points.push_back(p_offset.translated(Vector3(0, +cs, 0)).origin); - r_cursor_points.push_back(p_offset.translated(Vector3(0, -cs, 0)).origin); - r_cursor_points.push_back(p_offset.translated(Vector3(0, 0, +cs)).origin); - r_cursor_points.push_back(p_offset.translated(Vector3(0, 0, -cs)).origin); + r_cursor_points.push_back(p_offset.translated_local(Vector3(+cs, 0, 0)).origin); + r_cursor_points.push_back(p_offset.translated_local(Vector3(-cs, 0, 0)).origin); + r_cursor_points.push_back(p_offset.translated_local(Vector3(0, +cs, 0)).origin); + r_cursor_points.push_back(p_offset.translated_local(Vector3(0, -cs, 0)).origin); + r_cursor_points.push_back(p_offset.translated_local(Vector3(0, 0, +cs)).origin); + r_cursor_points.push_back(p_offset.translated_local(Vector3(0, 0, -cs)).origin); } void Joint3DGizmoPlugin::CreateHingeJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_limit_lower, real_t p_limit_upper, bool p_use_limit, Vector<Vector3> &r_common_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) { - r_common_points.push_back(p_offset.translated(Vector3(0, 0, 0.5)).origin); - r_common_points.push_back(p_offset.translated(Vector3(0, 0, -0.5)).origin); + r_common_points.push_back(p_offset.translated_local(Vector3(0, 0, 0.5)).origin); + r_common_points.push_back(p_offset.translated_local(Vector3(0, 0, -0.5)).origin); if (!p_use_limit) { p_limit_upper = -1; @@ -5057,34 +5675,34 @@ void Joint3DGizmoPlugin::CreateSliderJointGizmo(const Transform3D &p_offset, con p_linear_limit_upper = -p_linear_limit_upper; float cs = 0.25; - r_points.push_back(p_offset.translated(Vector3(0, 0, 0.5)).origin); - r_points.push_back(p_offset.translated(Vector3(0, 0, -0.5)).origin); + r_points.push_back(p_offset.translated_local(Vector3(0, 0, 0.5)).origin); + r_points.push_back(p_offset.translated_local(Vector3(0, 0, -0.5)).origin); if (p_linear_limit_lower >= p_linear_limit_upper) { - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, 0, 0)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, 0, 0)).origin); - - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, -cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, -cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, -cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, -cs)).origin); - - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, -cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, -cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, -cs)).origin); - r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, -cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, 0, 0)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, 0, 0)).origin); + + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, -cs, -cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, -cs, cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, -cs, cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, cs, cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, cs, cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, cs, -cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, cs, -cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_upper, -cs, -cs)).origin); + + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, -cs, -cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, -cs, cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, -cs, cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, cs, cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, cs, cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, cs, -cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, cs, -cs)).origin); + r_points.push_back(p_offset.translated_local(Vector3(p_linear_limit_lower, -cs, -cs)).origin); } else { - r_points.push_back(p_offset.translated(Vector3(+cs * 2, 0, 0)).origin); - r_points.push_back(p_offset.translated(Vector3(-cs * 2, 0, 0)).origin); + r_points.push_back(p_offset.translated_local(Vector3(+cs * 2, 0, 0)).origin); + r_points.push_back(p_offset.translated_local(Vector3(-cs * 2, 0, 0)).origin); } if (r_body_a_points) { @@ -5207,13 +5825,13 @@ void Joint3DGizmoPlugin::CreateGeneric6DOFJointGizmo( break; } -#define ADD_VTX(x, y, z) \ - { \ - Vector3 v; \ - v[a1] = (x); \ - v[a2] = (y); \ - v[a3] = (z); \ - r_points.push_back(p_offset.translated(v).origin); \ +#define ADD_VTX(x, y, z) \ + { \ + Vector3 v; \ + v[a1] = (x); \ + v[a2] = (y); \ + v[a3] = (z); \ + r_points.push_back(p_offset.translated_local(v).origin); \ } if (enable_lin && lll >= lul) { @@ -5298,16 +5916,16 @@ int FogVolumeGizmoPlugin::get_priority() const { return -1; } -String FogVolumeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { - return "Extents"; +String FogVolumeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + return "Size"; } -Variant FogVolumeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - return Vector3(p_gizmo->get_spatial_node()->call("get_extents")); +Variant FogVolumeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + return Vector3(p_gizmo->get_node_3d()->call("get_size")); } -void FogVolumeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - Node3D *sn = p_gizmo->get_spatial_node(); +void FogVolumeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + Node3D *sn = p_gizmo->get_node_3d(); Transform3D gt = sn->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -5321,7 +5939,7 @@ void FogVolumeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id axis[p_id] = 1.0; Vector3 ra, rb; Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); - float d = ra[p_id]; + float d = ra[p_id] * 2; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } @@ -5330,32 +5948,32 @@ void FogVolumeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id d = 0.001; } - Vector3 he = sn->call("get_extents"); + Vector3 he = sn->call("get_size"); he[p_id] = d; - sn->call("set_extents", he); + sn->call("set_size", he); } -void FogVolumeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - Node3D *sn = p_gizmo->get_spatial_node(); +void FogVolumeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + Node3D *sn = p_gizmo->get_node_3d(); if (p_cancel) { - sn->call("set_extents", p_restore); + sn->call("set_size", p_restore); return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Change Fog Volume Extents")); - ur->add_do_method(sn, "set_extents", sn->call("get_extents")); - ur->add_undo_method(sn, "set_extents", p_restore); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change Fog Volume Size")); + ur->add_do_method(sn, "set_size", sn->call("get_size")); + ur->add_undo_method(sn, "set_size", p_restore); ur->commit_action(); } void FogVolumeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Node3D *cs = p_gizmo->get_spatial_node(); + Node3D *cs = p_gizmo->get_node_3d(); p_gizmo->clear(); - if (RS::FogVolumeShape(int(p_gizmo->get_spatial_node()->call("get_shape"))) != RS::FOG_VOLUME_SHAPE_WORLD) { + if (RS::FogVolumeShape(int(p_gizmo->get_node_3d()->call("get_shape"))) != RS::FOG_VOLUME_SHAPE_WORLD) { const Ref<Material> material = get_material("shape_material", p_gizmo); const Ref<Material> material_internal = @@ -5365,8 +5983,8 @@ void FogVolumeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Vector<Vector3> lines; AABB aabb; - aabb.position = -cs->call("get_extents").operator Vector3(); - aabb.size = aabb.position * -2; + aabb.size = cs->call("get_size").operator Vector3(); + aabb.position = aabb.size / -2; for (int i = 0; i < 12; i++) { Vector3 a, b; @@ -5379,7 +5997,7 @@ void FogVolumeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { for (int i = 0; i < 3; i++) { Vector3 ax; - ax[i] = cs->call("get_extents").operator Vector3()[i]; + ax[i] = cs->call("get_size").operator Vector3()[i] / 2; handles.push_back(ax); } diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index a8383aefed..8cb0fe54d9 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -1,38 +1,38 @@ -/*************************************************************************/ -/* node_3d_editor_gizmos.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* node_3d_editor_gizmos.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 NODE_3D_EDITOR_GIZMOS_H #define NODE_3D_EDITOR_GIZMOS_H +#include "core/templates/hash_map.h" #include "core/templates/local_vector.h" -#include "core/templates/ordered_hash_map.h" #include "scene/3d/camera_3d.h" #include "scene/3d/node_3d.h" #include "scene/3d/skeleton_3d.h" @@ -70,22 +70,21 @@ class EditorNode3DGizmo : public Node3DGizmo { bool valid; bool hidden; Vector<Instance> instances; - Node3D *spatial_node; + Node3D *spatial_node = nullptr; - void _set_spatial_node(Node *p_node) { set_spatial_node(Object::cast_to<Node3D>(p_node)); } + void _set_node_3d(Node *p_node) { set_node_3d(Object::cast_to<Node3D>(p_node)); } protected: static void _bind_methods(); - EditorNode3DGizmoPlugin *gizmo_plugin; + EditorNode3DGizmoPlugin *gizmo_plugin = nullptr; GDVIRTUAL0(_redraw) - GDVIRTUAL1RC(String, _get_handle_name, int) - GDVIRTUAL1RC(bool, _is_handle_highlighted, int) - - GDVIRTUAL1RC(Variant, _get_handle_value, int) - GDVIRTUAL3(_set_handle, int, const Camera3D *, Vector2) - GDVIRTUAL3(_commit_handle, int, Variant, bool) + GDVIRTUAL2RC(String, _get_handle_name, int, bool) + GDVIRTUAL2RC(bool, _is_handle_highlighted, int, bool) + GDVIRTUAL2RC(Variant, _get_handle_value, int, bool) + GDVIRTUAL4(_set_handle, int, bool, const Camera3D *, Vector2) + GDVIRTUAL4(_commit_handle, int, bool, Variant, bool) GDVIRTUAL2RC(int, _subgizmos_intersect_ray, const Camera3D *, Vector2) GDVIRTUAL2RC(Vector<int>, _subgizmos_intersect_frustum, const Camera3D *, TypedArray<Plane>) @@ -100,13 +99,13 @@ public: void add_collision_triangles(const Ref<TriangleMesh> &p_tmesh); void add_unscaled_billboard(const Ref<Material> &p_material, real_t p_scale = 1, const Color &p_modulate = Color(1, 1, 1)); void add_handles(const Vector<Vector3> &p_handles, const Ref<Material> &p_material, const Vector<int> &p_ids = Vector<int>(), bool p_billboard = false, bool p_secondary = false); - void add_solid_box(Ref<Material> &p_material, Vector3 p_size, Vector3 p_position = Vector3(), const Transform3D &p_xform = Transform3D()); + void add_solid_box(const Ref<Material> &p_material, Vector3 p_size, Vector3 p_position = Vector3(), const Transform3D &p_xform = Transform3D()); - virtual bool is_handle_highlighted(int p_id) const; - virtual String get_handle_name(int p_id) const; - virtual Variant get_handle_value(int p_id) const; - virtual void set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point); - virtual void commit_handle(int p_id, const Variant &p_restore, bool p_cancel = false); + virtual bool is_handle_highlighted(int p_id, bool p_secondary) const; + virtual String get_handle_name(int p_id, bool p_secondary) const; + virtual Variant get_handle_value(int p_id, bool p_secondary) const; + virtual void set_handle(int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point); + virtual void commit_handle(int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false); virtual int subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 &p_point) const; virtual Vector<int> subgizmos_intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum) const; @@ -117,11 +116,11 @@ public: void set_selected(bool p_selected) { selected = p_selected; } bool is_selected() const { return selected; } - void set_spatial_node(Node3D *p_node); - Node3D *get_spatial_node() const { return spatial_node; } + void set_node_3d(Node3D *p_node); + Node3D *get_node_3d() const { return spatial_node; } Ref<EditorNode3DGizmoPlugin> get_plugin() const { return gizmo_plugin; } bool intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum); - void handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id); + void handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id, bool &r_secondary); bool intersect_ray(Camera3D *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal); bool is_subgizmo_selected(int p_id) const; Vector<int> get_subgizmo_selection() const; @@ -167,12 +166,12 @@ protected: GDVIRTUAL0RC(bool, _is_selectable_when_hidden) GDVIRTUAL1(_redraw, Ref<EditorNode3DGizmo>) - GDVIRTUAL2RC(String, _get_handle_name, Ref<EditorNode3DGizmo>, int) - GDVIRTUAL2RC(bool, _is_handle_highlighted, Ref<EditorNode3DGizmo>, int) - GDVIRTUAL2RC(Variant, _get_handle_value, Ref<EditorNode3DGizmo>, int) + GDVIRTUAL3RC(String, _get_handle_name, Ref<EditorNode3DGizmo>, int, bool) + GDVIRTUAL3RC(bool, _is_handle_highlighted, Ref<EditorNode3DGizmo>, int, bool) + GDVIRTUAL3RC(Variant, _get_handle_value, Ref<EditorNode3DGizmo>, int, bool) - GDVIRTUAL4(_set_handle, Ref<EditorNode3DGizmo>, int, const Camera3D *, Vector2) - GDVIRTUAL4(_commit_handle, Ref<EditorNode3DGizmo>, int, Variant, bool) + GDVIRTUAL5(_set_handle, Ref<EditorNode3DGizmo>, int, bool, const Camera3D *, Vector2) + GDVIRTUAL5(_commit_handle, Ref<EditorNode3DGizmo>, int, bool, Variant, bool) GDVIRTUAL3RC(int, _subgizmos_intersect_ray, Ref<EditorNode3DGizmo>, const Camera3D *, Vector2) GDVIRTUAL3RC(Vector<int>, _subgizmos_intersect_frustum, Ref<EditorNode3DGizmo>, const Camera3D *, TypedArray<Plane>) @@ -194,11 +193,11 @@ public: virtual bool is_selectable_when_hidden() const; virtual void redraw(EditorNode3DGizmo *p_gizmo); - virtual bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const; - virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const; - virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const; - virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point); - virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false); + virtual bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const; + virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const; + virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const; + virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point); + virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false); virtual int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const; virtual Vector<int> subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const; @@ -223,10 +222,10 @@ public: String get_gizmo_name() const override; int get_priority() const override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; void redraw(EditorNode3DGizmo *p_gizmo) override; Light3DGizmoPlugin(); @@ -240,10 +239,10 @@ public: String get_gizmo_name() const override; int get_priority() const override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; void redraw(EditorNode3DGizmo *p_gizmo) override; AudioStreamPlayer3DGizmoPlugin(); @@ -265,15 +264,18 @@ public: class Camera3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(Camera3DGizmoPlugin, EditorNode3DGizmoPlugin); +private: + static Size2i _get_viewport_size(Camera3D *p_camera); + public: bool has_gizmo(Node3D *p_spatial) override; String get_gizmo_name() const override; int get_priority() const override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; void redraw(EditorNode3DGizmo *p_gizmo) override; Camera3DGizmoPlugin(); @@ -301,6 +303,11 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; + OccluderInstance3DGizmoPlugin(); }; @@ -317,11 +324,23 @@ public: Sprite3DGizmoPlugin(); }; -class Position3DGizmoPlugin : public EditorNode3DGizmoPlugin { - GDCLASS(Position3DGizmoPlugin, EditorNode3DGizmoPlugin); +class Label3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(Label3DGizmoPlugin, EditorNode3DGizmoPlugin); + +public: + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + bool can_be_hidden() const override; + void redraw(EditorNode3DGizmo *p_gizmo) override; + + Label3DGizmoPlugin(); +}; + +class Marker3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(Marker3DGizmoPlugin, EditorNode3DGizmoPlugin); Ref<ArrayMesh> pos3d_mesh; - Vector<Vector3> cursor_points; public: bool has_gizmo(Node3D *p_spatial) override; @@ -329,7 +348,7 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - Position3DGizmoPlugin(); + Marker3DGizmoPlugin(); }; class PhysicalBone3DGizmoPlugin : public EditorNode3DGizmoPlugin { @@ -356,6 +375,18 @@ public: RayCast3DGizmoPlugin(); }; +class ShapeCast3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(ShapeCast3DGizmoPlugin, EditorNode3DGizmoPlugin); + +public: + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + void redraw(EditorNode3DGizmo *p_gizmo) override; + + ShapeCast3DGizmoPlugin(); +}; + class SpringArm3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(SpringArm3DGizmoPlugin, EditorNode3DGizmoPlugin); @@ -380,8 +411,8 @@ public: VehicleWheel3DGizmoPlugin(); }; -class SoftDynamicBody3DGizmoPlugin : public EditorNode3DGizmoPlugin { - GDCLASS(SoftDynamicBody3DGizmoPlugin, EditorNode3DGizmoPlugin); +class SoftBody3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(SoftBody3DGizmoPlugin, EditorNode3DGizmoPlugin); public: bool has_gizmo(Node3D *p_spatial) override; @@ -390,12 +421,12 @@ public: bool is_selectable_when_hidden() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; - bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; + bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; - SoftDynamicBody3DGizmoPlugin(); + SoftBody3DGizmoPlugin(); }; class VisibleOnScreenNotifier3DGizmoPlugin : public EditorNode3DGizmoPlugin { @@ -407,10 +438,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; VisibleOnScreenNotifier3DGizmoPlugin(); }; @@ -437,10 +468,10 @@ public: bool is_selectable_when_hidden() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; GPUParticles3DGizmoPlugin(); }; @@ -454,10 +485,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; GPUParticlesCollision3DGizmoPlugin(); }; @@ -471,10 +502,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; ReflectionProbeGizmoPlugin(); }; @@ -488,10 +519,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; DecalGizmoPlugin(); }; @@ -505,10 +536,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; VoxelGIGizmoPlugin(); }; @@ -522,11 +553,6 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; - LightmapGIGizmoPlugin(); }; @@ -539,11 +565,6 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; - LightmapProbeGizmoPlugin(); }; @@ -568,10 +589,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; CollisionShape3DGizmoPlugin(); }; @@ -594,7 +615,13 @@ class NavigationRegion3DGizmoPlugin : public EditorNode3DGizmoPlugin { Vector3 from; Vector3 to; - bool operator<(const _EdgeKey &p_with) const { return from == p_with.from ? to < p_with.to : from < p_with.from; } + static uint32_t hash(const _EdgeKey &p_key) { + return HashMapHasherDefault::hash(p_key.from) ^ HashMapHasherDefault::hash(p_key.to); + } + + bool operator==(const _EdgeKey &p_with) const { + return HashMapComparatorDefault<Vector3>::compare(from, p_with.from) && HashMapComparatorDefault<Vector3>::compare(to, p_with.to); + } }; public: @@ -606,6 +633,23 @@ public: NavigationRegion3DGizmoPlugin(); }; +class NavigationLink3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(NavigationLink3DGizmoPlugin, EditorNode3DGizmoPlugin); + +public: + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + void redraw(EditorNode3DGizmo *p_gizmo) override; + + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; + + NavigationLink3DGizmoPlugin(); +}; + class JointGizmosDrawer { public: static Basis look_body(const Transform3D &p_joint_transform, const Transform3D &p_body_transform); @@ -624,7 +668,7 @@ public: class Joint3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(Joint3DGizmoPlugin, EditorNode3DGizmoPlugin); - Timer *update_timer; + Timer *update_timer = nullptr; uint64_t update_idx = 0; void incremental_update_gizmos(); @@ -678,10 +722,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; FogVolumeGizmoPlugin(); }; diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index c86f6c02cb..fc1d8936d5 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -1,108 +1,289 @@ -/*************************************************************************/ -/* node_3d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* node_3d_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 "node_3d_editor_plugin.h" #include "core/config/project_settings.h" #include "core/input/input.h" -#include "core/math/camera_matrix.h" +#include "core/input/input_map.h" #include "core/math/math_funcs.h" +#include "core/math/projection.h" #include "core/os/keyboard.h" -#include "core/templates/sort_array.h" #include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/node_3d_editor_gizmos.h" -#include "editor/plugins/script_editor_plugin.h" +#include "editor/scene_tree_dock.h" #include "scene/3d/camera_3d.h" #include "scene/3d/collision_shape_3d.h" +#include "scene/3d/decal.h" #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/physics_body_3d.h" #include "scene/3d/visual_instance_3d.h" #include "scene/3d/world_environment.h" #include "scene/gui/center_container.h" +#include "scene/gui/color_picker.h" +#include "scene/gui/flow_container.h" +#include "scene/gui/split_container.h" #include "scene/gui/subviewport_container.h" #include "scene/resources/packed_scene.h" +#include "scene/resources/sky_material.h" #include "scene/resources/surface_tool.h" -#define DISTANCE_DEFAULT 4 +constexpr real_t DISTANCE_DEFAULT = 4; -#define GIZMO_ARROW_SIZE 0.35 -#define GIZMO_RING_HALF_WIDTH 0.1 -#define GIZMO_PLANE_SIZE 0.2 -#define GIZMO_PLANE_DST 0.3 -#define GIZMO_CIRCLE_SIZE 1.1 -#define GIZMO_SCALE_OFFSET (GIZMO_CIRCLE_SIZE + 0.3) -#define GIZMO_ARROW_OFFSET (GIZMO_CIRCLE_SIZE + 0.3) +constexpr real_t GIZMO_ARROW_SIZE = 0.35; +constexpr real_t GIZMO_RING_HALF_WIDTH = 0.1; +constexpr real_t GIZMO_PLANE_SIZE = 0.2; +constexpr real_t GIZMO_PLANE_DST = 0.3; +constexpr real_t GIZMO_CIRCLE_SIZE = 1.1; +constexpr real_t GIZMO_SCALE_OFFSET = GIZMO_CIRCLE_SIZE + 0.3; +constexpr real_t GIZMO_ARROW_OFFSET = GIZMO_CIRCLE_SIZE + 0.3; -#define ZOOM_FREELOOK_MIN 0.01 -#define ZOOM_FREELOOK_MULTIPLIER 1.08 -#define ZOOM_FREELOOK_INDICATOR_DELAY_S 1.5 +constexpr real_t ZOOM_FREELOOK_MIN = 0.01; +constexpr real_t ZOOM_FREELOOK_MULTIPLIER = 1.08; +constexpr real_t ZOOM_FREELOOK_INDICATOR_DELAY_S = 1.5; #ifdef REAL_T_IS_DOUBLE -#define ZOOM_FREELOOK_MAX 1'000'000'000'000 +constexpr double ZOOM_FREELOOK_MAX = 1'000'000'000'000; #else -#define ZOOM_FREELOOK_MAX 10'000 +constexpr float ZOOM_FREELOOK_MAX = 10'000; #endif -#define MIN_Z 0.01 -#define MAX_Z 1000000.0 +constexpr real_t MIN_Z = 0.01; +constexpr real_t MAX_Z = 1000000.0; -#define MIN_FOV 0.01 -#define MAX_FOV 179 +constexpr real_t MIN_FOV = 0.01; +constexpr real_t MAX_FOV = 179; -void ViewportRotationControl::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - axis_menu_options.clear(); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_RIGHT); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_TOP); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_LEFT); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_BOTTOM); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT); +void ViewportNavigationControl::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (!is_connected("mouse_exited", callable_mp(this, &ViewportNavigationControl::_on_mouse_exited))) { + connect("mouse_exited", callable_mp(this, &ViewportNavigationControl::_on_mouse_exited)); + } + if (!is_connected("mouse_entered", callable_mp(this, &ViewportNavigationControl::_on_mouse_entered))) { + connect("mouse_entered", callable_mp(this, &ViewportNavigationControl::_on_mouse_entered)); + } + } break; + + case NOTIFICATION_DRAW: { + if (viewport != nullptr) { + _draw(); + _update_navigation(); + } + } break; + } +} + +void ViewportNavigationControl::_draw() { + if (nav_mode == Node3DEditorViewport::NAVIGATION_NONE) { + return; + } + + Vector2 center = get_size() / 2.0; + float radius = get_size().x / 2.0; - axis_colors.clear(); - axis_colors.push_back(get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); - axis_colors.push_back(get_theme_color(SNAME("axis_y_color"), SNAME("Editor"))); - axis_colors.push_back(get_theme_color(SNAME("axis_z_color"), SNAME("Editor"))); - update(); + const bool focused = focused_index != -1; + draw_circle(center, radius, Color(0.5, 0.5, 0.5, focused || hovered ? 0.35 : 0.15)); - if (!is_connected("mouse_exited", callable_mp(this, &ViewportRotationControl::_on_mouse_exited))) { - connect("mouse_exited", callable_mp(this, &ViewportRotationControl::_on_mouse_exited)); + const Color c = focused ? Color(0.9, 0.9, 0.9, 0.9) : Color(0.5, 0.5, 0.5, 0.25); + + Vector2 circle_pos = focused ? center.move_toward(focused_pos, radius) : center; + + draw_circle(circle_pos, AXIS_CIRCLE_RADIUS, c); + draw_circle(circle_pos, AXIS_CIRCLE_RADIUS * 0.8, c.darkened(0.4)); +} + +void ViewportNavigationControl::_process_click(int p_index, Vector2 p_position, bool p_pressed) { + hovered = false; + queue_redraw(); + + if (focused_index != -1 && focused_index != p_index) { + return; + } + if (p_pressed) { + if (p_position.distance_to(get_size() / 2.0) < get_size().x / 2.0) { + focused_pos = p_position; + focused_index = p_index; + queue_redraw(); } + } else { + focused_index = -1; + if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + Input::get_singleton()->warp_mouse(focused_mouse_start); + } + } +} + +void ViewportNavigationControl::_process_drag(int p_index, Vector2 p_position, Vector2 p_relative_position) { + if (focused_index == p_index) { + if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_VISIBLE) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); + focused_mouse_start = p_position; + } + focused_pos += p_relative_position; + queue_redraw(); + } +} + +void ViewportNavigationControl::gui_input(const Ref<InputEvent> &p_event) { + // Mouse events + const Ref<InputEventMouseButton> mouse_button = p_event; + if (mouse_button.is_valid() && mouse_button->get_button_index() == MouseButton::LEFT) { + _process_click(100, mouse_button->get_position(), mouse_button->is_pressed()); + } + + const Ref<InputEventMouseMotion> mouse_motion = p_event; + if (mouse_motion.is_valid()) { + _process_drag(100, mouse_motion->get_global_position(), viewport->_get_warped_mouse_motion(mouse_motion)); + } + + // Touch events + const Ref<InputEventScreenTouch> screen_touch = p_event; + if (screen_touch.is_valid()) { + _process_click(screen_touch->get_index(), screen_touch->get_position(), screen_touch->is_pressed()); + } + + const Ref<InputEventScreenDrag> screen_drag = p_event; + if (screen_drag.is_valid()) { + _process_drag(screen_drag->get_index(), screen_drag->get_position(), screen_drag->get_relative()); + } +} + +void ViewportNavigationControl::_update_navigation() { + if (focused_index == -1) { + return; + } + + Vector2 delta = focused_pos - (get_size() / 2.0); + Vector2 delta_normalized = delta.normalized(); + switch (nav_mode) { + case Node3DEditorViewport::NavigationMode::NAVIGATION_MOVE: { + real_t speed_multiplier = MIN(delta.length() / (get_size().x * 100.0), 3.0); + real_t speed = viewport->freelook_speed * speed_multiplier; + + const Node3DEditorViewport::FreelookNavigationScheme navigation_scheme = (Node3DEditorViewport::FreelookNavigationScheme)EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_navigation_scheme").operator int(); + + Vector3 forward; + if (navigation_scheme == Node3DEditorViewport::FreelookNavigationScheme::FREELOOK_FULLY_AXIS_LOCKED) { + // Forward/backward keys will always go straight forward/backward, never moving on the Y axis. + forward = Vector3(0, 0, delta_normalized.y).rotated(Vector3(0, 1, 0), viewport->camera->get_rotation().y); + } else { + // Forward/backward keys will be relative to the camera pitch. + forward = viewport->camera->get_transform().basis.xform(Vector3(0, 0, delta_normalized.y)); + } + + const Vector3 right = viewport->camera->get_transform().basis.xform(Vector3(delta_normalized.x, 0, 0)); + + const Vector3 direction = forward + right; + const Vector3 motion = direction * speed; + viewport->cursor.pos += motion; + viewport->cursor.eye_pos += motion; + } break; + + case Node3DEditorViewport::NavigationMode::NAVIGATION_LOOK: { + real_t speed_multiplier = MIN(delta.length() / (get_size().x * 2.5), 3.0); + real_t speed = viewport->freelook_speed * speed_multiplier; + viewport->_nav_look(nullptr, delta_normalized * speed); + } break; + + case Node3DEditorViewport::NAVIGATION_PAN: { + real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0); + real_t speed = viewport->freelook_speed * speed_multiplier; + viewport->_nav_pan(nullptr, -delta_normalized * speed); + } break; + case Node3DEditorViewport::NAVIGATION_ZOOM: { + real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0); + real_t speed = viewport->freelook_speed * speed_multiplier; + viewport->_nav_zoom(nullptr, delta_normalized * speed); + } break; + case Node3DEditorViewport::NAVIGATION_ORBIT: { + real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0); + real_t speed = viewport->freelook_speed * speed_multiplier; + viewport->_nav_orbit(nullptr, delta_normalized * speed); + } break; + case Node3DEditorViewport::NAVIGATION_NONE: { + } break; } +} + +void ViewportNavigationControl::_on_mouse_entered() { + hovered = true; + queue_redraw(); +} + +void ViewportNavigationControl::_on_mouse_exited() { + hovered = false; + queue_redraw(); +} + +void ViewportNavigationControl::set_navigation_mode(Node3DEditorViewport::NavigationMode p_nav_mode) { + nav_mode = p_nav_mode; +} - if (p_what == NOTIFICATION_DRAW && viewport != nullptr) { - _draw(); +void ViewportNavigationControl::set_viewport(Node3DEditorViewport *p_viewport) { + viewport = p_viewport; +} + +void ViewportRotationControl::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + axis_menu_options.clear(); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_RIGHT); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_TOP); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_LEFT); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_BOTTOM); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT); + + axis_colors.clear(); + axis_colors.push_back(get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); + axis_colors.push_back(get_theme_color(SNAME("axis_y_color"), SNAME("Editor"))); + axis_colors.push_back(get_theme_color(SNAME("axis_z_color"), SNAME("Editor"))); + queue_redraw(); + + if (!is_connected("mouse_exited", callable_mp(this, &ViewportRotationControl::_on_mouse_exited))) { + connect("mouse_exited", callable_mp(this, &ViewportRotationControl::_on_mouse_exited)); + } + } break; + + case NOTIFICATION_DRAW: { + if (viewport != nullptr) { + _draw(); + } + } break; } } @@ -110,7 +291,7 @@ void ViewportRotationControl::_draw() { const Vector2i center = get_size() / 2.0; const real_t radius = get_size().x / 2.0; - if (focused_axis > -2 || orbiting) { + if (focused_axis > -2 || orbiting_index != -1) { draw_circle(center, radius, Color(0.5, 0.5, 0.5, 0.25)); } @@ -139,7 +320,7 @@ void ViewportRotationControl::_draw_axis(const Axis2D &p_axis) { // Draw the axis letter for the positive axes. const String axis_name = direction == 0 ? "X" : (direction == 1 ? "Y" : "Z"); - draw_char(get_theme_font(SNAME("rotation_control"), SNAME("EditorFonts")), p_axis.screen_point + Vector2i(-4, 5) * EDSCALE, axis_name, "", get_theme_font_size(SNAME("rotation_control_size"), SNAME("EditorFonts")), Color(0.0, 0.0, 0.0, alpha)); + draw_char(get_theme_font(SNAME("rotation_control"), SNAME("EditorFonts")), p_axis.screen_point + Vector2i(Math::round(-4.0 * EDSCALE), Math::round(5.0 * EDSCALE)), axis_name, get_theme_font_size(SNAME("rotation_control_size"), SNAME("EditorFonts")), Color(0.0, 0.0, 0.0, alpha)); } else { // Draw an outline around the negative axes. draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c); @@ -153,7 +334,7 @@ void ViewportRotationControl::_get_sorted_axis(Vector<Axis2D> &r_axis) { const Basis camera_basis = viewport->to_camera_transform(viewport->cursor).get_basis().inverse(); for (int i = 0; i < 3; ++i) { - Vector3 axis_3d = camera_basis.get_axis(i); + Vector3 axis_3d = camera_basis.get_column(i); Vector2i axis_vector = Vector2(axis_3d.x, -axis_3d.y) * radius; if (Math::abs(axis_3d.z) < 1.0) { @@ -181,41 +362,63 @@ void ViewportRotationControl::_get_sorted_axis(Vector<Axis2D> &r_axis) { r_axis.sort_custom<Axis2DCompare>(); } +void ViewportRotationControl::_process_click(int p_index, Vector2 p_position, bool p_pressed) { + if (orbiting_index != -1 && orbiting_index != p_index) { + return; + } + if (p_pressed) { + if (p_position.distance_to(get_size() / 2.0) < get_size().x / 2.0) { + orbiting_index = p_index; + } + } else { + if (focused_axis > -1) { + viewport->_menu_option(axis_menu_options[focused_axis]); + _update_focus(); + } + orbiting_index = -1; + if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + Input::get_singleton()->warp_mouse(orbiting_mouse_start); + } + } +} + +void ViewportRotationControl::_process_drag(Ref<InputEventWithModifiers> p_event, int p_index, Vector2 p_position, Vector2 p_relative_position) { + if (orbiting_index == p_index) { + if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_VISIBLE) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); + orbiting_mouse_start = p_position; + } + viewport->_nav_orbit(p_event, p_relative_position); + focused_axis = -1; + } else { + _update_focus(); + } +} + void ViewportRotationControl::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); + // Mouse events const Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { - Vector2 pos = mb->get_position(); - if (mb->is_pressed()) { - if (pos.distance_to(get_size() / 2.0) < get_size().x / 2.0) { - orbiting = true; - } - } else { - if (focused_axis > -1) { - viewport->_menu_option(axis_menu_options[focused_axis]); - _update_focus(); - } - orbiting = false; - if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) { - Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); - Input::get_singleton()->warp_mouse_position(orbiting_mouse_start); - } - } + _process_click(100, mb->get_position(), mb->is_pressed()); } const Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - if (orbiting) { - if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_VISIBLE) { - Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); - orbiting_mouse_start = mm->get_global_position(); - } - viewport->_nav_orbit(mm, viewport->_get_warped_mouse_motion(mm)); - focused_axis = -1; - } else { - _update_focus(); - } + _process_drag(mm, 100, mm->get_global_position(), viewport->_get_warped_mouse_motion(mm)); + } + + // Touch events + const Ref<InputEventScreenTouch> screen_touch = p_event; + if (screen_touch.is_valid()) { + _process_click(screen_touch->get_index(), screen_touch->get_position(), screen_touch->is_pressed()); + } + + const Ref<InputEventScreenDrag> screen_drag = p_event; + if (screen_drag.is_valid()) { + _process_drag(screen_drag, screen_drag->get_index(), screen_drag->get_position(), screen_drag->get_relative()); } } @@ -239,13 +442,13 @@ void ViewportRotationControl::_update_focus() { } if (focused_axis != original_focus) { - update(); + queue_redraw(); } } void ViewportRotationControl::_on_mouse_exited() { focused_axis = -2; - update(); + queue_redraw(); } void ViewportRotationControl::set_viewport(Node3DEditorViewport *p_viewport) { @@ -260,6 +463,15 @@ void Node3DEditorViewport::_view_settings_confirmed(real_t p_interp_delta) { _update_camera(p_interp_delta); } +void Node3DEditorViewport::_update_navigation_controls_visibility() { + bool show_viewport_rotation_gizmo = EDITOR_GET("editors/3d/navigation/show_viewport_rotation_gizmo") && (!previewing_cinema && !previewing_camera); + rotation_control->set_visible(show_viewport_rotation_gizmo); + + bool show_viewport_navigation_gizmo = EDITOR_GET("editors/3d/navigation/show_viewport_navigation_gizmo") && (!previewing_cinema && !previewing_camera); + position_control->set_visible(show_viewport_navigation_gizmo); + look_control->set_visible(show_viewport_navigation_gizmo); +} + void Node3DEditorViewport::_update_camera(real_t p_interp_delta) { bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL; @@ -334,7 +546,7 @@ void Node3DEditorViewport::_update_camera(real_t p_interp_delta) { camera->set_global_transform(to_camera_transform(camera_cursor)); if (orthogonal) { - float half_fov = Math::deg2rad(get_fov()) / 2.0; + float half_fov = Math::deg_to_rad(get_fov()) / 2.0; float height = 2.0 * cursor.distance * Math::tan(half_fov); camera->set_orthogonal(height, get_znear(), get_zfar()); } else { @@ -342,28 +554,30 @@ void Node3DEditorViewport::_update_camera(real_t p_interp_delta) { } update_transform_gizmo_view(); - rotation_control->update(); + rotation_control->queue_redraw(); + position_control->queue_redraw(); + look_control->queue_redraw(); spatial_editor->update_grid(); } } Transform3D Node3DEditorViewport::to_camera_transform(const Cursor &p_cursor) const { Transform3D camera_transform; - camera_transform.translate(p_cursor.pos); + camera_transform.translate_local(p_cursor.pos); camera_transform.basis.rotate(Vector3(1, 0, 0), -p_cursor.x_rot); camera_transform.basis.rotate(Vector3(0, 1, 0), -p_cursor.y_rot); if (orthogonal) { - camera_transform.translate(0, 0, (get_zfar() - get_znear()) / 2.0); + camera_transform.translate_local(0, 0, (get_zfar() - get_znear()) / 2.0); } else { - camera_transform.translate(0, 0, p_cursor.distance); + camera_transform.translate_local(0, 0, p_cursor.distance); } return camera_transform; } int Node3DEditorViewport::get_selected_count() const { - Map<Node *, Object *> &selection = editor_selection->get_selection(); + const HashMap<Node *, Object *> &selection = editor_selection->get_selection(); int count = 0; @@ -384,6 +598,33 @@ int Node3DEditorViewport::get_selected_count() const { return count; } +void Node3DEditorViewport::cancel_transform() { + List<Node *> &selection = editor_selection->get_selected_node_list(); + + for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { + Node3D *sp = Object::cast_to<Node3D>(E->get()); + if (!sp) { + continue; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } + + sp->set_global_transform(se->original); + } + + finish_transform(); + set_message(TTR("Transform Aborted."), 3); +} + +void Node3DEditorViewport::_update_shrink() { + bool shrink = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION)); + subviewport_container->set_stretch_shrink(shrink ? 2 : 1); + subviewport_container->set_texture_filter(shrink ? TEXTURE_FILTER_NEAREST : TEXTURE_FILTER_PARENT_NODE); +} + float Node3DEditorViewport::get_znear() const { return CLAMP(spatial_editor->get_znear(), MIN_Z, MAX_Z); } @@ -413,7 +654,7 @@ Vector3 Node3DEditorViewport::_get_ray_pos(const Vector2 &p_pos) const { } Vector3 Node3DEditorViewport::_get_camera_normal() const { - return -_get_camera_transform().basis.get_axis(2); + return -_get_camera_transform().basis.get_column(2); } Vector3 Node3DEditorViewport::_get_ray(const Vector2 &p_pos) const { @@ -423,6 +664,7 @@ Vector3 Node3DEditorViewport::_get_ray(const Vector2 &p_pos) const { void Node3DEditorViewport::_clear_selected() { _edit.gizmo = Ref<EditorNode3DGizmo>(); _edit.gizmo_handle = -1; + _edit.gizmo_handle_secondary = false; _edit.gizmo_initial_value = Variant(); Node3D *selected = spatial_editor->get_single_selected_node(); @@ -450,7 +692,7 @@ void Node3DEditorViewport::_select_clicked(bool p_allow_locked) { if (!p_allow_locked) { // Replace the node by the group if grouped - while (node && node != editor->get_edited_scene()->get_parent()) { + while (node && node != EditorNode::get_singleton()->get_edited_scene()->get_parent()) { Node3D *selected_tmp = Object::cast_to<Node3D>(node); if (selected_tmp && node->has_meta("_edit_group_")) { selected = selected_tmp; @@ -470,17 +712,17 @@ void Node3DEditorViewport::_select_clicked(bool p_allow_locked) { if (!editor_selection->is_selected(selected)) { editor_selection->clear(); editor_selection->add_node(selected); - editor->edit_node(selected); + EditorNode::get_singleton()->edit_node(selected); } } if (editor_selection->get_selected_node_list().size() == 1) { - editor->edit_node(editor_selection->get_selected_node_list()[0]); + EditorNode::get_singleton()->edit_node(editor_selection->get_selected_node_list()[0]); } } } -ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) { +ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) const { Vector3 ray = _get_ray(p_pos); Vector3 pos = _get_ray_pos(p_pos); Vector2 shrinked_pos = p_pos / subviewport_container->get_stretch_shrink(); @@ -490,7 +732,7 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) { } Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, pos + ray * camera->get_far(), get_tree()->get_root()->get_world_3d()->get_scenario()); - Set<Ref<EditorNode3DGizmo>> found_gizmos; + HashSet<Ref<EditorNode3DGizmo>> found_gizmos; Node *edited_scene = get_tree()->get_edited_scene_root(); ObjectID closest; @@ -553,7 +795,7 @@ void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, Vector<_RayRe Vector3 pos = _get_ray_pos(p_pos); Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, pos + ray * camera->get_far(), get_tree()->get_root()->get_world_3d()->get_scenario()); - Set<Node3D *> found_nodes; + HashSet<Node3D *> found_nodes; for (int i = 0; i < instances.size(); i++) { Node3D *spat = Object::cast_to<Node3D>(ObjectDB::get_instance(instances[i])); @@ -607,7 +849,7 @@ void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, Vector<_RayRe } Vector3 Node3DEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) { - CameraMatrix cm; + Projection cm; if (orthogonal) { cm.set_orthogonal(camera->get_size(), get_size().aspect(), get_znear() + p_vector3.z, get_zfar()); } else { @@ -616,10 +858,10 @@ Vector3 Node3DEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) { Vector2 screen_he = cm.get_viewport_half_extents(); Transform3D camera_transform; - camera_transform.translate(cursor.pos); + camera_transform.translate_local(cursor.pos); camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot); camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot); - camera_transform.translate(0, 0, cursor.distance); + camera_transform.translate_local(0, 0, cursor.distance); return camera_transform.xform(Vector3(((p_vector3.x / get_size().width) * 2.0 - 1.0) * screen_he.x, ((1.0 - (p_vector3.y / get_size().height)) * 2.0 - 1.0) * screen_he.y, -(get_znear() + p_vector3.z))); } @@ -736,7 +978,7 @@ void Node3DEditorViewport::_select_region() { } Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_convex(frustum, get_tree()->get_root()->get_world_3d()->get_scenario()); - Set<Node3D *> found_nodes; + HashSet<Node3D *> found_nodes; Vector<Node *> selected; Node *edited_scene = get_tree()->get_edited_scene_root(); @@ -761,7 +1003,7 @@ void Node3DEditorViewport::_select_region() { // Replace the node by the group if grouped if (item->is_class("Node3D")) { Node3D *sel = Object::cast_to<Node3D>(item); - while (item && item != editor->get_edited_scene()->get_parent()) { + while (item && item != EditorNode::get_singleton()->get_edited_scene()->get_parent()) { Node3D *selected_tmp = Object::cast_to<Node3D>(item); if (selected_tmp && item->has_meta("_edit_group_")) { sel = selected_tmp; @@ -795,7 +1037,7 @@ void Node3DEditorViewport::_select_region() { } if (editor_selection->get_selected_node_list().size() == 1) { - editor->edit_node(editor_selection->get_selected_node_list()[0]); + EditorNode::get_singleton()->edit_node(editor_selection->get_selected_node_list()[0]); } } @@ -864,6 +1106,7 @@ void Node3DEditorViewport::_update_name() { } void Node3DEditorViewport::_compute_edit(const Point2 &p_point) { + _edit.original_local = spatial_editor->are_local_coords_enabled(); _edit.click_ray = _get_ray(p_point); _edit.click_ray_pos = _get_ray_pos(p_point); _edit.plane = TRANSFORM_VIEW; @@ -902,7 +1145,7 @@ void Node3DEditorViewport::_compute_edit(const Point2 &p_point) { } static Key _get_key_modifier_setting(const String &p_property) { - switch (EditorSettings::get_singleton()->get(p_property).operator int()) { + switch (EDITOR_GET(p_property).operator int()) { case 0: return Key::NONE; case 1: @@ -954,7 +1197,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b real_t col_d = 1e20; for (int i = 0; i < 3; i++) { - const Vector3 grabber_pos = gt.origin + gt.basis.get_axis(i) * gizmo_scale * (GIZMO_ARROW_OFFSET + (GIZMO_ARROW_SIZE * 0.5)); + const Vector3 grabber_pos = gt.origin + gt.basis.get_column(i).normalized() * gizmo_scale * (GIZMO_ARROW_OFFSET + (GIZMO_ARROW_SIZE * 0.5)); const real_t grabber_radius = gizmo_scale * GIZMO_ARROW_SIZE; Vector3 r; @@ -974,15 +1217,15 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b col_d = 1e20; for (int i = 0; i < 3; i++) { - Vector3 ivec2 = gt.basis.get_axis((i + 1) % 3).normalized(); - Vector3 ivec3 = gt.basis.get_axis((i + 2) % 3).normalized(); + Vector3 ivec2 = gt.basis.get_column((i + 1) % 3).normalized(); + Vector3 ivec3 = gt.basis.get_column((i + 2) % 3).normalized(); // Allow some tolerance to make the plane easier to click, // even if the click is actually slightly outside the plane. const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667); Vector3 r; - Plane plane(gt.basis.get_axis(i).normalized(), gt.origin); + Plane plane(gt.basis.get_column(i).normalized(), gt.origin); if (plane.intersects_ray(ray_pos, ray, &r)) { const real_t dist = r.distance_to(grabber_pos); @@ -1017,24 +1260,40 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { int col_axis = -1; - float col_d = 1e20; - for (int i = 0; i < 3; i++) { - Plane plane(gt.basis.get_axis(i).normalized(), gt.origin); - Vector3 r; - if (!plane.intersects_ray(ray_pos, ray, &r)) { - continue; + Vector3 hit_position; + Vector3 hit_normal; + real_t ray_length = gt.origin.distance_to(ray_pos) + (GIZMO_CIRCLE_SIZE * gizmo_scale) * 4.0f; + if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * ray_length, gt.origin, gizmo_scale * (GIZMO_CIRCLE_SIZE), &hit_position, &hit_normal)) { + if (hit_normal.dot(_get_camera_normal()) < 0.05) { + hit_position = gt.xform_inv(hit_position).abs(); + int min_axis = hit_position.min_axis_index(); + if (hit_position[min_axis] < gizmo_scale * GIZMO_RING_HALF_WIDTH) { + col_axis = min_axis; + } } + } + + if (col_axis == -1) { + float col_d = 1e20; + + for (int i = 0; i < 3; i++) { + Plane plane(gt.basis.get_column(i).normalized(), gt.origin); + Vector3 r; + if (!plane.intersects_ray(ray_pos, ray, &r)) { + continue; + } - const real_t dist = r.distance_to(gt.origin); - const Vector3 r_dir = (r - gt.origin).normalized(); + const real_t dist = r.distance_to(gt.origin); + const Vector3 r_dir = (r - gt.origin).normalized(); - if (_get_camera_normal().dot(r_dir) <= 0.005) { - if (dist > gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gizmo_scale * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) { - const real_t d = ray_pos.distance_to(r); - if (d < col_d) { - col_d = d; - col_axis = i; + if (_get_camera_normal().dot(r_dir) <= 0.005) { + if (dist > gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gizmo_scale * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) { + const real_t d = ray_pos.distance_to(r); + if (d < col_d) { + col_d = d; + col_axis = i; + } } } } @@ -1058,7 +1317,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b float col_d = 1e20; for (int i = 0; i < 3; i++) { - const Vector3 grabber_pos = gt.origin + gt.basis.get_axis(i) * gizmo_scale * GIZMO_SCALE_OFFSET; + const Vector3 grabber_pos = gt.origin + gt.basis.get_column(i).normalized() * gizmo_scale * GIZMO_SCALE_OFFSET; const real_t grabber_radius = gizmo_scale * GIZMO_ARROW_SIZE; Vector3 r; @@ -1078,15 +1337,15 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b col_d = 1e20; for (int i = 0; i < 3; i++) { - const Vector3 ivec2 = gt.basis.get_axis((i + 1) % 3).normalized(); - const Vector3 ivec3 = gt.basis.get_axis((i + 2) % 3).normalized(); + const Vector3 ivec2 = gt.basis.get_column((i + 1) % 3).normalized(); + const Vector3 ivec3 = gt.basis.get_column((i + 2) % 3).normalized(); // Allow some tolerance to make the plane easier to click, // even if the click is actually slightly outside the plane. const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667); Vector3 r; - Plane plane(gt.basis.get_axis(i).normalized(), gt.origin); + Plane plane(gt.basis.get_column(i).normalized(), gt.origin); if (plane.intersects_ray(ray_pos, ray, &r)) { const real_t dist = r.distance_to(grabber_pos); @@ -1138,68 +1397,59 @@ void Node3DEditorViewport::_transform_gizmo_apply(Node3D *p_node, const Transfor } } -Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local) { +Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local, bool p_orthogonal) { switch (p_mode) { case TRANSFORM_SCALE: { + if (_edit.snap || spatial_editor->is_snap_enabled()) { + p_motion.snap(Vector3(p_extra, p_extra, p_extra)); + } + Transform3D s; if (p_local) { - Basis g = p_original.basis.orthonormalized(); - Vector3 local_motion = g.inverse().xform(p_motion); - - if (_edit.snap || spatial_editor->is_snap_enabled()) { - local_motion.snap(Vector3(p_extra, p_extra, p_extra)); - } - - Transform3D local_t; - local_t.basis = p_original_local.basis.scaled_local(local_motion + Vector3(1, 1, 1)); - local_t.origin = p_original_local.origin; - return local_t; + s.basis = p_original_local.basis.scaled_local(p_motion + Vector3(1, 1, 1)); + s.origin = p_original_local.origin; } else { + s.basis.scale(p_motion + Vector3(1, 1, 1)); Transform3D base = Transform3D(Basis(), _edit.center); - if (_edit.snap || spatial_editor->is_snap_enabled()) { - p_motion.snap(Vector3(p_extra, p_extra, p_extra)); - } + s = base * (s * (base.inverse() * p_original)); - Transform3D global_t; - global_t.basis.scale(p_motion + Vector3(1, 1, 1)); - return base * (global_t * (base.inverse() * p_original)); + // Recalculate orthogonalized scale without moving origin. + if (p_orthogonal) { + s.basis = p_original.basis.scaled_orthogonal(p_motion + Vector3(1, 1, 1)); + } } + + return s; } case TRANSFORM_TRANSLATE: { - if (p_local) { - if (_edit.snap || spatial_editor->is_snap_enabled()) { - Basis g = p_original.basis.orthonormalized(); - Vector3 local_motion = g.inverse().xform(p_motion); - local_motion.snap(Vector3(p_extra, p_extra, p_extra)); - - p_motion = g.xform(local_motion); - } + if (_edit.snap || spatial_editor->is_snap_enabled()) { + p_motion.snap(Vector3(p_extra, p_extra, p_extra)); + } - } else { - if (_edit.snap || spatial_editor->is_snap_enabled()) { - p_motion.snap(Vector3(p_extra, p_extra, p_extra)); - } + if (p_local) { + p_motion = p_original.basis.xform(p_motion); } // Apply translation Transform3D t = p_original; t.origin += p_motion; + return t; } case TRANSFORM_ROTATE: { + Transform3D r; + if (p_local) { - Transform3D r; Vector3 axis = p_original_local.basis.xform(p_motion); r.basis = Basis(axis.normalized(), p_extra) * p_original_local.basis; r.origin = p_original_local.origin; - return r; } else { - Transform3D r; Basis local = p_original.basis * p_original_local.basis.inverse(); Vector3 axis = local.xform_inv(p_motion); r.basis = local * Basis(axis.normalized(), p_extra) * p_original_local.basis; r.origin = Basis(p_motion, p_extra).xform(p_original.origin - _edit.center) + _edit.center; - return r; } + + return r; } default: { ERR_FAIL_V_MSG(Transform3D(), "Invalid mode in '_compute_transform'"); @@ -1208,13 +1458,15 @@ Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const } void Node3DEditorViewport::_surface_mouse_enter() { - if (!surface->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field())) { + if (!surface->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) { surface->grab_focus(); } } void Node3DEditorViewport::_surface_mouse_exit() { - _remove_preview(); + _remove_preview_node(); + _reset_preview_material(); + _remove_preview_material(); } void Node3DEditorViewport::_surface_focus_enter() { @@ -1226,13 +1478,13 @@ void Node3DEditorViewport::_surface_focus_exit() { } bool Node3DEditorViewport ::_is_node_locked(const Node *p_node) { - return p_node->has_meta("_edit_lock_") && p_node->get_meta("_edit_lock_"); + return p_node->get_meta("_edit_lock_", false); } void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) { _find_items_at_pos(b->get_position(), selection_results, spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT); - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); for (int i = 0; i < selection_results.size(); i++) { Node3D *item = selection_results[i].item; @@ -1267,7 +1519,7 @@ void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) { if (_is_node_locked(spat)) { locked = 1; } else { - Node *ed_scene = editor->get_edited_scene(); + Node *ed_scene = EditorNode::get_singleton()->get_edited_scene(); Node *node = spat; while (node && node != ed_scene->get_parent()) { @@ -1279,7 +1531,7 @@ void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) { } } - String suffix = String(); + String suffix; if (locked == 1) { suffix = " (" + TTR("Locked") + ")"; } else if (locked == 2) { @@ -1304,28 +1556,28 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS; { - EditorNode *en = editor; + EditorNode *en = EditorNode::get_singleton(); EditorPluginList *force_input_forwarding_list = en->get_editor_plugins_force_input_forwarding(); if (!force_input_forwarding_list->is_empty()) { - EditorPlugin::AfterGUIInput discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true); + EditorPlugin::AfterGUIInput discard = force_input_forwarding_list->forward_3d_gui_input(camera, p_event, true); if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) { return; } - if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) { - after = EditorPlugin::AFTER_GUI_INPUT_DESELECT; + if (discard == EditorPlugin::AFTER_GUI_INPUT_CUSTOM) { + after = EditorPlugin::AFTER_GUI_INPUT_CUSTOM; } } } { - EditorNode *en = editor; + EditorNode *en = EditorNode::get_singleton(); EditorPluginList *over_plugin_list = en->get_editor_plugins_over(); if (!over_plugin_list->is_empty()) { - EditorPlugin::AfterGUIInput discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false); + EditorPlugin::AfterGUIInput discard = over_plugin_list->forward_3d_gui_input(camera, p_event, false); if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) { return; } - if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) { - after = EditorPlugin::AFTER_GUI_INPUT_DESELECT; + if (discard == EditorPlugin::AFTER_GUI_INPUT_CUSTOM) { + after = EditorPlugin::AFTER_GUI_INPUT_CUSTOM; } } } @@ -1338,33 +1590,25 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { const real_t zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor(); switch (b->get_button_index()) { case MouseButton::WHEEL_UP: { - if (b->is_alt_pressed()) { - scale_fov(-0.05); + if (is_freelook_active()) { + scale_freelook_speed(zoom_factor); } else { - if (is_freelook_active()) { - scale_freelook_speed(zoom_factor); - } else { - scale_cursor_distance(1.0 / zoom_factor); - } + scale_cursor_distance(1.0 / zoom_factor); } } break; case MouseButton::WHEEL_DOWN: { - if (b->is_alt_pressed()) { - scale_fov(0.05); + if (is_freelook_active()) { + scale_freelook_speed(1.0 / zoom_factor); } else { - if (is_freelook_active()) { - scale_freelook_speed(1.0 / zoom_factor); - } else { - scale_cursor_distance(zoom_factor); - } + scale_cursor_distance(zoom_factor); } } break; case MouseButton::RIGHT: { - NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int(); + NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int(); if (b->is_pressed() && _edit.gizmo.is_valid()) { //restore - _edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_initial_value, true); + _edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true); _edit.gizmo = Ref<EditorNode3DGizmo>(); } @@ -1380,39 +1624,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (_edit.mode != TRANSFORM_NONE && b->is_pressed()) { - //cancel motion - _edit.mode = TRANSFORM_NONE; - - List<Node *> &selection = editor_selection->get_selected_node_list(); - - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (se->gizmo.is_valid()) { - Vector<int> ids; - Vector<Transform3D> restore; - - for (const KeyValue<int, Transform3D> &GE : se->subgizmos) { - ids.push_back(GE.key); - restore.push_back(GE.value); - } - - se->gizmo->commit_subgizmos(ids, restore, true); - spatial_editor->update_transform_gizmo(); - } else { - sp->set_global_transform(se->original); - } - } - surface->update(); - set_message(TTR("Transform Aborted."), 3); + cancel_transform(); } if (b->is_pressed()) { @@ -1466,7 +1678,13 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } break; case MouseButton::LEFT: { if (b->is_pressed()) { - NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int(); + clicked_wants_append = b->is_shift_pressed(); + + if (_edit.mode != TRANSFORM_NONE && _edit.instant) { + commit_transform(); + break; // just commit the edit, stop processing the event so we don't deselect the object + } + NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int(); if ((nav_scheme == NAVIGATION_MAYA || nav_scheme == NAVIGATION_MODO) && b->is_alt_pressed()) { break; } @@ -1480,6 +1698,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { _edit.original_mouse_pos = b->get_position(); _edit.snap = spatial_editor->is_snap_enabled(); _edit.mode = TRANSFORM_NONE; + _edit.original = spatial_editor->get_gizmo_transform(); // To prevent to break when flipping with scale. bool can_select_gizmos = spatial_editor->get_single_selected_node(); @@ -1501,11 +1720,13 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } int gizmo_handle = -1; - seg->handles_intersect_ray(camera, _edit.mouse_pos, b->is_shift_pressed(), gizmo_handle); + bool gizmo_secondary = false; + seg->handles_intersect_ray(camera, _edit.mouse_pos, b->is_shift_pressed(), gizmo_handle, gizmo_secondary); if (gizmo_handle != -1) { _edit.gizmo = seg; _edit.gizmo_handle = gizmo_handle; - _edit.gizmo_initial_value = seg->get_handle_value(gizmo_handle); + _edit.gizmo_handle_secondary = gizmo_secondary; + _edit.gizmo_initial_value = seg->get_handle_value(gizmo_handle, gizmo_secondary); intersected_handle = true; break; } @@ -1568,43 +1789,25 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { clicked = ObjectID(); - if ((spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT && b->is_command_pressed()) || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { - /* HANDLE ROTATION */ - if (get_selected_count() == 0) { - break; //bye - } - //handle rotate - _edit.mode = TRANSFORM_ROTATE; - _compute_edit(b->get_position()); + if ((spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT && b->is_command_or_control_pressed()) || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { + begin_transform(TRANSFORM_ROTATE, false); break; } if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) { - if (get_selected_count() == 0) { - break; //bye - } - //handle translate - _edit.mode = TRANSFORM_TRANSLATE; - _compute_edit(b->get_position()); + begin_transform(TRANSFORM_TRANSLATE, false); break; } if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE) { - if (get_selected_count() == 0) { - break; //bye - } - //handle scale - _edit.mode = TRANSFORM_SCALE; - _compute_edit(b->get_position()); + begin_transform(TRANSFORM_SCALE, false); break; } - if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) { - clicked = _select_ray(b->get_position()); - + if (after != EditorPlugin::AFTER_GUI_INPUT_CUSTOM) { //clicking is always deferred to either move or release - - clicked_wants_append = b->is_shift_pressed(); + clicked = _select_ray(b->get_position()); + selection_in_progress = true; if (clicked.is_null()) { //default to regionselect @@ -1614,15 +1817,17 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } - surface->update(); + surface->queue_redraw(); } else { if (_edit.gizmo.is_valid()) { - _edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_initial_value, false); + _edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, false); _edit.gizmo = Ref<EditorNode3DGizmo>(); break; } - if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + if (after != EditorPlugin::AFTER_GUI_INPUT_CUSTOM) { + selection_in_progress = false; + if (clicked.is_valid()) { _select_clicked(false); } @@ -1630,7 +1835,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (cursor.region_select) { _select_region(); cursor.region_select = false; - surface->update(); + surface->queue_redraw(); } } @@ -1648,39 +1853,14 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } se->gizmo->commit_subgizmos(ids, restore, false); - spatial_editor->update_transform_gizmo(); } else { - static const char *_transform_name[4] = { - TTRC("None"), - TTRC("Rotate"), - // TRANSLATORS: This refers to the movement that changes the position of an object. - TTRC("Translate"), - TTRC("Scale"), - }; - undo_redo->create_action(TTRGET(_transform_name[_edit.mode])); - - List<Node *> &selection = editor_selection->get_selected_node_list(); - - for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { - Node3D *sp = Object::cast_to<Node3D>(E->get()); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!sel_item) { - continue; - } - - undo_redo->add_do_method(sp, "set_global_transform", sp->get_global_gizmo_transform()); - undo_redo->add_undo_method(sp, "set_global_transform", sel_item->original); - } - undo_redo->commit_action(); + commit_transform(); } _edit.mode = TRANSFORM_NONE; set_message(""); + spatial_editor->update_transform_gizmo(); } - surface->update(); + surface->queue_redraw(); } } break; @@ -1699,6 +1879,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Ref<EditorNode3DGizmo> found_gizmo; int found_handle = -1; + bool found_handle_secondary = false; for (int i = 0; i < gizmos.size(); i++) { Ref<EditorNode3DGizmo> seg = gizmos[i]; @@ -1706,7 +1887,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { continue; } - seg->handles_intersect_ray(camera, _edit.mouse_pos, false, found_handle); + seg->handles_intersect_ray(camera, _edit.mouse_pos, false, found_handle, found_handle_secondary); if (found_handle != -1) { found_gizmo = seg; @@ -1718,27 +1899,29 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { spatial_editor->select_gizmo_highlight_axis(-1); } - if (found_gizmo != spatial_editor->get_current_hover_gizmo() || found_handle != spatial_editor->get_current_hover_gizmo_handle()) { + bool current_hover_handle_secondary = false; + int curreny_hover_handle = spatial_editor->get_current_hover_gizmo_handle(current_hover_handle_secondary); + if (found_gizmo != spatial_editor->get_current_hover_gizmo() || found_handle != curreny_hover_handle || found_handle_secondary != current_hover_handle_secondary) { spatial_editor->set_current_hover_gizmo(found_gizmo); - spatial_editor->set_current_hover_gizmo_handle(found_handle); + spatial_editor->set_current_hover_gizmo_handle(found_handle, found_handle_secondary); spatial_editor->get_single_selected_node()->update_gizmos(); } } - if (spatial_editor->get_current_hover_gizmo().is_null() && (m->get_button_mask() & MouseButton::MASK_LEFT) == MouseButton::NONE && !_edit.gizmo.is_valid()) { + if (spatial_editor->get_current_hover_gizmo().is_null() && !m->get_button_mask().has_flag(MouseButtonMask::LEFT) && !_edit.gizmo.is_valid()) { _transform_gizmo_select(_edit.mouse_pos, true); } - NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int(); + NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int(); NavigationMode nav_mode = NAVIGATION_NONE; if (_edit.gizmo.is_valid()) { - _edit.gizmo->set_handle(_edit.gizmo_handle, camera, m->get_position()); - Variant v = _edit.gizmo->get_handle_value(_edit.gizmo_handle); - String n = _edit.gizmo->get_handle_name(_edit.gizmo_handle); + _edit.gizmo->set_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, camera, m->get_position()); + Variant v = _edit.gizmo->get_handle_value(_edit.gizmo_handle, _edit.gizmo_handle_secondary); + String n = _edit.gizmo->get_handle_name(_edit.gizmo_handle, _edit.gizmo_handle_secondary); set_message(n + ": " + String(v)); - } else if ((m->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { + } else if (m->get_button_mask().has_flag(MouseButtonMask::LEFT) || _edit.instant) { if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) { nav_mode = NAVIGATION_ORBIT; } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_shift_pressed()) { @@ -1749,341 +1932,35 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { nav_mode = NAVIGATION_ORBIT; } else { const bool movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 8 * EDSCALE; - if (clicked.is_valid() && movement_threshold_passed) { - _compute_edit(_edit.original_mouse_pos); - clicked = ObjectID(); - _edit.mode = TRANSFORM_TRANSLATE; + // enable region-select if nothing has been selected yet or multi-select (shift key) is active + if (selection_in_progress && movement_threshold_passed) { + if (get_selected_count() == 0 || clicked_wants_append) { + cursor.region_select = true; + cursor.region_begin = _edit.original_mouse_pos; + clicked = ObjectID(); + } } if (cursor.region_select) { cursor.region_end = m->get_position(); - surface->update(); + surface->queue_redraw(); return; } + if (clicked.is_valid() && movement_threshold_passed) { + _compute_edit(_edit.original_mouse_pos); + clicked = ObjectID(); + _edit.mode = TRANSFORM_TRANSLATE; + } + if (_edit.mode == TRANSFORM_NONE) { return; } - Vector3 ray_pos = _get_ray_pos(m->get_position()); - Vector3 ray = _get_ray(m->get_position()); - double snap = EDITOR_GET("interface/inspector/default_float_step"); - int snap_step_decimals = Math::range_step_decimals(snap); - - switch (_edit.mode) { - case TRANSFORM_SCALE: { - Vector3 motion_mask; - Plane plane; - bool plane_mv = false; - - switch (_edit.plane) { - case TRANSFORM_VIEW: - motion_mask = Vector3(0, 0, 0); - plane = Plane(_get_camera_normal(), _edit.center); - break; - case TRANSFORM_X_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); - break; - case TRANSFORM_Y_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); - break; - case TRANSFORM_Z_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2); - plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); - break; - case TRANSFORM_YZ: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0), _edit.center); - plane_mv = true; - break; - case TRANSFORM_XZ: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1), _edit.center); - plane_mv = true; - break; - case TRANSFORM_XY: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0) + spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2), _edit.center); - plane_mv = true; - break; - } - - Vector3 intersection; - if (!plane.intersects_ray(ray_pos, ray, &intersection)) { - break; - } - - Vector3 click; - if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) { - break; - } - - Vector3 motion = intersection - click; - if (_edit.plane != TRANSFORM_VIEW) { - if (!plane_mv) { - motion = motion_mask.dot(motion) * motion_mask; - - } else { - // Alternative planar scaling mode - if (_get_key_modifier(m) != Key::SHIFT) { - motion = motion_mask.dot(motion) * motion_mask; - } - } - - } else { - const real_t center_click_dist = click.distance_to(_edit.center); - const real_t center_inters_dist = intersection.distance_to(_edit.center); - if (center_click_dist == 0) { - break; - } - - const real_t scale = center_inters_dist - center_click_dist; - motion = Vector3(scale, scale, scale); - } - - motion /= click.distance_to(_edit.center); - - // Disable local transformation for TRANSFORM_VIEW - bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); - - if (_edit.snap || spatial_editor->is_snap_enabled()) { - snap = spatial_editor->get_scale_snap() / 100; - } - Vector3 motion_snapped = motion; - motion_snapped.snap(Vector3(snap, snap, snap)); - // This might not be necessary anymore after issue #288 is solved (in 4.0?). - set_message(TTR("Scaling: ") + "(" + String::num(motion_snapped.x, snap_step_decimals) + ", " + - String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")"); - - List<Node *> &selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (sp->has_meta("_edit_lock_")) { - continue; - } - - if (se->gizmo.is_valid()) { - for (KeyValue<int, Transform3D> &GE : se->subgizmos) { - Transform3D xform = GE.value; - Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords); - if (!local_coords) { - new_xform = se->original.affine_inverse() * new_xform; - } - se->gizmo->set_subgizmo_transform(GE.key, new_xform); - } - } else { - Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords); - _transform_gizmo_apply(se->sp, new_xform, local_coords); - } - } - - spatial_editor->update_transform_gizmo(); - surface->update(); - - } break; - - case TRANSFORM_TRANSLATE: { - Vector3 motion_mask; - Plane plane; - bool plane_mv = false; - - switch (_edit.plane) { - case TRANSFORM_VIEW: - plane = Plane(_get_camera_normal(), _edit.center); - break; - case TRANSFORM_X_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); - break; - case TRANSFORM_Y_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); - break; - case TRANSFORM_Z_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2); - plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); - break; - case TRANSFORM_YZ: - plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0), _edit.center); - plane_mv = true; - break; - case TRANSFORM_XZ: - plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1), _edit.center); - plane_mv = true; - break; - case TRANSFORM_XY: - plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2), _edit.center); - plane_mv = true; - break; - } - - Vector3 intersection; - if (!plane.intersects_ray(ray_pos, ray, &intersection)) { - break; - } - - Vector3 click; - if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) { - break; - } - - Vector3 motion = intersection - click; - if (_edit.plane != TRANSFORM_VIEW) { - if (!plane_mv) { - motion = motion_mask.dot(motion) * motion_mask; - } - } - - // Disable local transformation for TRANSFORM_VIEW - bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); - - if (_edit.snap || spatial_editor->is_snap_enabled()) { - snap = spatial_editor->get_translate_snap(); - } - Vector3 motion_snapped = motion; - motion_snapped.snap(Vector3(snap, snap, snap)); - set_message(TTR("Translating: ") + "(" + String::num(motion_snapped.x, snap_step_decimals) + ", " + - String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")"); - - List<Node *> &selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (sp->has_meta("_edit_lock_")) { - continue; - } - - if (se->gizmo.is_valid()) { - for (KeyValue<int, Transform3D> &GE : se->subgizmos) { - Transform3D xform = GE.value; - Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords); - new_xform = se->original.affine_inverse() * new_xform; - se->gizmo->set_subgizmo_transform(GE.key, new_xform); - } - } else { - Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original, se->original_local, motion, snap, local_coords); - _transform_gizmo_apply(se->sp, new_xform, false); - } - } - - spatial_editor->update_transform_gizmo(); - surface->update(); - - } break; - - case TRANSFORM_ROTATE: { - Plane plane; - Vector3 axis; - - switch (_edit.plane) { - case TRANSFORM_VIEW: - plane = Plane(_get_camera_normal(), _edit.center); - break; - case TRANSFORM_X_AXIS: - plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0), _edit.center); - axis = Vector3(1, 0, 0); - break; - case TRANSFORM_Y_AXIS: - plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1), _edit.center); - axis = Vector3(0, 1, 0); - break; - case TRANSFORM_Z_AXIS: - plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2), _edit.center); - axis = Vector3(0, 0, 1); - break; - case TRANSFORM_YZ: - case TRANSFORM_XZ: - case TRANSFORM_XY: - break; - } - - Vector3 intersection; - if (!plane.intersects_ray(ray_pos, ray, &intersection)) { - break; - } - - Vector3 click; - if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) { - break; - } - - Vector3 y_axis = (click - _edit.center).normalized(); - Vector3 x_axis = plane.normal.cross(y_axis).normalized(); - - double angle = Math::atan2(x_axis.dot(intersection - _edit.center), y_axis.dot(intersection - _edit.center)); - - if (_edit.snap || spatial_editor->is_snap_enabled()) { - snap = spatial_editor->get_rotate_snap(); - } - angle = Math::rad2deg(angle) + snap * 0.5; //else it won't reach +180 - angle -= Math::fmod(angle, snap); - set_message(vformat(TTR("Rotating %s degrees."), String::num(angle, snap_step_decimals))); - angle = Math::deg2rad(angle); - - bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW - - List<Node *> &selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (sp->has_meta("_edit_lock_")) { - continue; - } - - Vector3 compute_axis = local_coords ? axis : plane.normal; - if (se->gizmo.is_valid()) { - for (KeyValue<int, Transform3D> &GE : se->subgizmos) { - Transform3D xform = GE.value; - - Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords); - if (!local_coords) { - new_xform = se->original.affine_inverse() * new_xform; - } - se->gizmo->set_subgizmo_transform(GE.key, new_xform); - } - } else { - Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original, se->original_local, compute_axis, angle, local_coords); - _transform_gizmo_apply(se->sp, new_xform, local_coords); - } - } - - spatial_editor->update_transform_gizmo(); - surface->update(); - - } break; - default: { - } - } + update_transform(m->get_position(), _get_key_modifier(m) == Key::SHIFT); } - } else if ((m->get_button_mask() & MouseButton::MASK_RIGHT) != MouseButton::NONE || freelook_active) { + } else if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) || freelook_active) { if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) { nav_mode = NAVIGATION_ZOOM; } else if (freelook_active) { @@ -2092,7 +1969,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { nav_mode = NAVIGATION_PAN; } - } else if ((m->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE) { + } else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) { const Key mod = _get_key_modifier(m); if (nav_scheme == NAVIGATION_GODOT) { if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) { @@ -2108,7 +1985,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { nav_mode = NAVIGATION_PAN; } } - } else if (EditorSettings::get_singleton()->get("editors/3d/navigation/emulate_3_button_mouse")) { + } else if (EDITOR_GET("editors/3d/navigation/emulate_3_button_mouse")) { // Handle trackpad (no external mouse) use case const Key mod = _get_key_modifier(m); @@ -2161,7 +2038,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Ref<InputEventPanGesture> pan_gesture = p_event; if (pan_gesture.is_valid()) { - NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int(); + NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int(); NavigationMode nav_mode = NAVIGATION_NONE; if (nav_scheme == NAVIGATION_GODOT) { @@ -2215,13 +2092,63 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; } - if (EditorSettings::get_singleton()->get("editors/3d/navigation/emulate_numpad")) { - const Key code = k->get_keycode(); + if (EDITOR_GET("editors/3d/navigation/emulate_numpad")) { + const Key code = k->get_physical_keycode(); if (code >= Key::KEY_0 && code <= Key::KEY_9) { k->set_keycode(code - Key::KEY_0 + Key::KP_0); } } + if (_edit.mode == TRANSFORM_NONE) { + if (k->get_keycode() == Key::ESCAPE && !cursor.region_select) { + _clear_selected(); + return; + } + } else { + // We're actively transforming, handle keys specially + TransformPlane new_plane = TRANSFORM_VIEW; + String new_message; + if (ED_IS_SHORTCUT("spatial_editor/lock_transform_x", p_event)) { + new_plane = TRANSFORM_X_AXIS; + new_message = TTR("X-Axis Transform."); + } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_y", p_event)) { + new_plane = TRANSFORM_Y_AXIS; + new_message = TTR("Y-Axis Transform."); + } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_z", p_event)) { + new_plane = TRANSFORM_Z_AXIS; + new_message = TTR("Z-Axis Transform."); + } else if (_edit.mode != TRANSFORM_ROTATE) { // rotating on a plane doesn't make sense + if (ED_IS_SHORTCUT("spatial_editor/lock_transform_yz", p_event)) { + new_plane = TRANSFORM_YZ; + new_message = TTR("YZ-Plane Transform."); + } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xz", p_event)) { + new_plane = TRANSFORM_XZ; + new_message = TTR("XZ-Plane Transform."); + } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xy", p_event)) { + new_plane = TRANSFORM_XY; + new_message = TTR("XY-Plane Transform."); + } + } + + if (new_plane != TRANSFORM_VIEW) { + if (new_plane != _edit.plane) { + // lock me once and get a global constraint + _edit.plane = new_plane; + spatial_editor->set_local_coords_enabled(false); + } else if (!spatial_editor->are_local_coords_enabled()) { + // lock me twice and get a local constraint + spatial_editor->set_local_coords_enabled(true); + } else { + // lock me thrice and we're back where we started + _edit.plane = TRANSFORM_VIEW; + spatial_editor->set_local_coords_enabled(false); + } + update_transform(_edit.mouse_pos, Input::get_singleton()->is_key_pressed(Key::SHIFT)); + set_message(new_message, 2); + accept_event(); + return; + } + } if (ED_IS_SHORTCUT("spatial_editor/snap", p_event)) { if (_edit.mode != TRANSFORM_NONE) { _edit.snap = !_edit.snap; @@ -2278,11 +2205,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (ED_IS_SHORTCUT("spatial_editor/focus_selection", p_event)) { _menu_option(VIEW_CENTER_TO_SELECTION); } - // Orthgonal mode doesn't work in freelook. - if (!freelook_active && ED_IS_SHORTCUT("spatial_editor/switch_perspective_orthogonal", p_event)) { - _menu_option(orthogonal ? VIEW_PERSPECTIVE : VIEW_ORTHOGONAL); - _update_name(); - } if (ED_IS_SHORTCUT("spatial_editor/align_transform_with_view", p_event)) { _menu_option(VIEW_ALIGN_TRANSFORM_WITH_VIEW); } @@ -2312,6 +2234,20 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { set_message(TTR("Animation Key Inserted.")); } + if (ED_IS_SHORTCUT("spatial_editor/cancel_transform", p_event) && _edit.mode != TRANSFORM_NONE) { + cancel_transform(); + } + if (!is_freelook_active()) { + if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event)) { + begin_transform(TRANSFORM_TRANSLATE, true); + } + if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event)) { + begin_transform(TRANSFORM_ROTATE, true); + } + if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event)) { + begin_transform(TRANSFORM_SCALE, true); + } + } // Freelook doesn't work in orthogonal mode. if (!orthogonal && ED_IS_SHORTCUT("spatial_editor/freelook_toggle", p_event)) { @@ -2348,40 +2284,38 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) { - const NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int(); + const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int(); real_t pan_speed = 1 / 150.0; - int pan_speed_modifier = 10; - if (nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) { - pan_speed *= pan_speed_modifier; + if (p_event.is_valid() && nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) { + pan_speed *= 10; } Transform3D camera_transform; - camera_transform.translate(cursor.pos); + camera_transform.translate_local(cursor.pos); camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot); camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot); - const bool invert_x_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_x_axis"); - const bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis"); + const bool invert_x_axis = EDITOR_GET("editors/3d/navigation/invert_x_axis"); + const bool invert_y_axis = EDITOR_GET("editors/3d/navigation/invert_y_axis"); Vector3 translation( (invert_x_axis ? -1 : 1) * -p_relative.x * pan_speed, (invert_y_axis ? -1 : 1) * p_relative.y * pan_speed, 0); translation *= cursor.distance / DISTANCE_DEFAULT; - camera_transform.translate(translation); + camera_transform.translate_local(translation); cursor.pos = camera_transform.origin; } void Node3DEditorViewport::_nav_zoom(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) { - const NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int(); + const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int(); real_t zoom_speed = 1 / 80.0; - int zoom_speed_modifier = 10; - if (nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) { - zoom_speed *= zoom_speed_modifier; + if (p_event.is_valid() && nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) { + zoom_speed *= 10; } - NavigationZoomStyle zoom_style = (NavigationZoomStyle)EditorSettings::get_singleton()->get("editors/3d/navigation/zoom_style").operator int(); + NavigationZoomStyle zoom_style = (NavigationZoomStyle)EDITOR_GET("editors/3d/navigation/zoom_style").operator int(); if (zoom_style == NAVIGATION_ZOOM_HORIZONTAL) { if (p_relative.x > 0) { scale_cursor_distance(1 - p_relative.x * zoom_speed); @@ -2407,10 +2341,10 @@ void Node3DEditorViewport::_nav_orbit(Ref<InputEventWithModifiers> p_event, cons _menu_option(VIEW_PERSPECTIVE); } - const real_t degrees_per_pixel = EditorSettings::get_singleton()->get("editors/3d/navigation_feel/orbit_sensitivity"); - const real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel); - const bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis"); - const bool invert_x_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_x_axis"); + const real_t degrees_per_pixel = EDITOR_GET("editors/3d/navigation_feel/orbit_sensitivity"); + const real_t radians_per_pixel = Math::deg_to_rad(degrees_per_pixel); + const bool invert_y_axis = EDITOR_GET("editors/3d/navigation/invert_y_axis"); + const bool invert_x_axis = EDITOR_GET("editors/3d/navigation/invert_x_axis"); if (invert_y_axis) { cursor.x_rot -= p_relative.y * radians_per_pixel; @@ -2440,9 +2374,9 @@ void Node3DEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const } // Scale mouse sensitivity with camera FOV scale when zoomed in to make it easier to point at things. - const real_t degrees_per_pixel = real_t(EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_sensitivity")) * MIN(1.0, cursor.fov_scale); - const real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel); - const bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis"); + const real_t degrees_per_pixel = real_t(EDITOR_GET("editors/3d/freelook/freelook_sensitivity")) * MIN(1.0, cursor.fov_scale); + const real_t radians_per_pixel = Math::deg_to_rad(degrees_per_pixel); + const bool invert_y_axis = EDITOR_GET("editors/3d/navigation/invert_y_axis"); // Note: do NOT assume the camera has the "current" transform, because it is interpolated and may have "lag". const Transform3D prev_camera_transform = to_camera_transform(cursor); @@ -2479,9 +2413,9 @@ void Node3DEditorViewport::set_freelook_active(bool active_now) { // Also sync the camera cursor, otherwise switching to freelook will be trippy if inertia is active camera_cursor.eye_pos = cursor.eye_pos; - if (EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_speed_zoom_link")) { + if (EDITOR_GET("editors/3d/freelook/freelook_speed_zoom_link")) { // Re-adjust freelook speed from the current zoom level - real_t base_speed = EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_base_speed"); + real_t base_speed = EDITOR_GET("editors/3d/freelook/freelook_base_speed"); freelook_speed = base_speed * cursor.distance; } @@ -2508,12 +2442,12 @@ void Node3DEditorViewport::set_freelook_active(bool active_now) { void Node3DEditorViewport::scale_fov(real_t p_fov_offset) { cursor.fov_scale = CLAMP(cursor.fov_scale + p_fov_offset, 0.1, 2.5); - surface->update(); + surface->queue_redraw(); } void Node3DEditorViewport::reset_fov() { cursor.fov_scale = 1.0; - surface->update(); + surface->queue_redraw(); } void Node3DEditorViewport::scale_cursor_distance(real_t scale) { @@ -2532,7 +2466,7 @@ void Node3DEditorViewport::scale_cursor_distance(real_t scale) { } zoom_indicator_delay = ZOOM_FREELOOK_INDICATOR_DELAY_S; - surface->update(); + surface->queue_redraw(); } void Node3DEditorViewport::scale_freelook_speed(real_t scale) { @@ -2545,12 +2479,12 @@ void Node3DEditorViewport::scale_freelook_speed(real_t scale) { } zoom_indicator_delay = ZOOM_FREELOOK_INDICATOR_DELAY_S; - surface->update(); + surface->queue_redraw(); } Point2i Node3DEditorViewport::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const { Point2i relative; - if (bool(EDITOR_DEF("editors/3d/navigation/warped_mouse_panning", false))) { + if (bool(EDITOR_GET("editors/3d/navigation/warped_mouse_panning"))) { relative = Input::get_singleton()->warp_mouse_motion(p_ev_mouse_motion, surface->get_global_rect()); } else { relative = p_ev_mouse_motion->get_relative(); @@ -2558,32 +2492,12 @@ Point2i Node3DEditorViewport::_get_warped_mouse_motion(const Ref<InputEventMouse return relative; } -static bool is_shortcut_pressed(const String &p_path) { - Ref<Shortcut> shortcut = ED_GET_SHORTCUT(p_path); - if (shortcut.is_null()) { - return false; - } - - const Array shortcuts = shortcut->get_events(); - Ref<InputEventKey> k; - if (shortcuts.size() > 0) { - k = shortcuts.front(); - } - - if (k.is_null()) { - return false; - } - const Input &input = *Input::get_singleton(); - Key keycode = k->get_keycode(); - return input.is_key_pressed(keycode); -} - void Node3DEditorViewport::_update_freelook(real_t delta) { if (!is_freelook_active()) { return; } - const FreelookNavigationScheme navigation_scheme = (FreelookNavigationScheme)EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_navigation_scheme").operator int(); + const FreelookNavigationScheme navigation_scheme = (FreelookNavigationScheme)EDITOR_GET("editors/3d/freelook/freelook_navigation_scheme").operator int(); Vector3 forward; if (navigation_scheme == FREELOOK_FULLY_AXIS_LOCKED) { @@ -2607,31 +2521,34 @@ void Node3DEditorViewport::_update_freelook(real_t delta) { Vector3 direction; - if (is_shortcut_pressed("spatial_editor/freelook_left")) { + // Use actions from the inputmap, as this is the only way to reliably detect input in this method. + // See #54469 for more discussion and explanation. + Input *inp = Input::get_singleton(); + if (inp->is_action_pressed("spatial_editor/freelook_left")) { direction -= right; } - if (is_shortcut_pressed("spatial_editor/freelook_right")) { + if (inp->is_action_pressed("spatial_editor/freelook_right")) { direction += right; } - if (is_shortcut_pressed("spatial_editor/freelook_forward")) { + if (inp->is_action_pressed("spatial_editor/freelook_forward")) { direction += forward; } - if (is_shortcut_pressed("spatial_editor/freelook_backwards")) { + if (inp->is_action_pressed("spatial_editor/freelook_backwards")) { direction -= forward; } - if (is_shortcut_pressed("spatial_editor/freelook_up")) { + if (inp->is_action_pressed("spatial_editor/freelook_up")) { direction += up; } - if (is_shortcut_pressed("spatial_editor/freelook_down")) { + if (inp->is_action_pressed("spatial_editor/freelook_down")) { direction -= up; } real_t speed = freelook_speed; - if (is_shortcut_pressed("spatial_editor/freelook_speed_modifier")) { + if (inp->is_action_pressed("spatial_editor/freelook_speed_modifier")) { speed *= 3.0; } - if (is_shortcut_pressed("spatial_editor/freelook_slow_modifier")) { + if (inp->is_action_pressed("spatial_editor/freelook_slow_modifier")) { speed *= 0.333333; } @@ -2656,32 +2573,34 @@ void Node3DEditorPlugin::edited_scene_changed() { void Node3DEditorViewport::_project_settings_changed() { //update shadow atlas if changed - int shadowmap_size = ProjectSettings::get_singleton()->get("rendering/shadows/shadow_atlas/size"); - bool shadowmap_16_bits = ProjectSettings::get_singleton()->get("rendering/shadows/shadow_atlas/16_bits"); - int atlas_q0 = ProjectSettings::get_singleton()->get("rendering/shadows/shadow_atlas/quadrant_0_subdiv"); - int atlas_q1 = ProjectSettings::get_singleton()->get("rendering/shadows/shadow_atlas/quadrant_1_subdiv"); - int atlas_q2 = ProjectSettings::get_singleton()->get("rendering/shadows/shadow_atlas/quadrant_2_subdiv"); - int atlas_q3 = ProjectSettings::get_singleton()->get("rendering/shadows/shadow_atlas/quadrant_3_subdiv"); - - viewport->set_shadow_atlas_size(shadowmap_size); - viewport->set_shadow_atlas_16_bits(shadowmap_16_bits); - viewport->set_shadow_atlas_quadrant_subdiv(0, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q0)); - viewport->set_shadow_atlas_quadrant_subdiv(1, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q1)); - viewport->set_shadow_atlas_quadrant_subdiv(2, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q2)); - viewport->set_shadow_atlas_quadrant_subdiv(3, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q3)); - - bool shrink = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION)); - - if (shrink != (subviewport_container->get_stretch_shrink() > 1)) { - subviewport_container->set_stretch_shrink(shrink ? 2 : 1); - } + int shadowmap_size = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_size"); + bool shadowmap_16_bits = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_16_bits"); + int atlas_q0 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_0_subdiv"); + int atlas_q1 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_1_subdiv"); + int atlas_q2 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_2_subdiv"); + int atlas_q3 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_3_subdiv"); + + viewport->set_positional_shadow_atlas_size(shadowmap_size); + viewport->set_positional_shadow_atlas_16_bits(shadowmap_16_bits); + viewport->set_positional_shadow_atlas_quadrant_subdiv(0, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q0)); + viewport->set_positional_shadow_atlas_quadrant_subdiv(1, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q1)); + viewport->set_positional_shadow_atlas_quadrant_subdiv(2, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q2)); + viewport->set_positional_shadow_atlas_quadrant_subdiv(3, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q3)); + + _update_shrink(); // Update MSAA, screen-space AA and debanding if changed - const int msaa_mode = ProjectSettings::get_singleton()->get("rendering/anti_aliasing/quality/msaa"); - viewport->set_msaa(Viewport::MSAA(msaa_mode)); + const int msaa_mode = GLOBAL_GET("rendering/anti_aliasing/quality/msaa_3d"); + viewport->set_msaa_3d(Viewport::MSAA(msaa_mode)); const int ssaa_mode = GLOBAL_GET("rendering/anti_aliasing/quality/screen_space_aa"); viewport->set_screen_space_aa(Viewport::ScreenSpaceAA(ssaa_mode)); + const bool use_taa = GLOBAL_GET("rendering/anti_aliasing/quality/use_taa"); + viewport->set_use_taa(use_taa); + + const bool transparent_background = GLOBAL_GET("rendering/viewport/transparent_background"); + viewport->set_transparent_background(transparent_background); + const bool use_debanding = GLOBAL_GET("rendering/anti_aliasing/quality/use_debanding"); viewport->set_use_debanding(use_debanding); @@ -2690,316 +2609,355 @@ void Node3DEditorViewport::_project_settings_changed() { const float mesh_lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels"); viewport->set_mesh_lod_threshold(mesh_lod_threshold); + + const Viewport::Scaling3DMode scaling_3d_mode = Viewport::Scaling3DMode(int(GLOBAL_GET("rendering/scaling_3d/mode"))); + viewport->set_scaling_3d_mode(scaling_3d_mode); + + const float scaling_3d_scale = GLOBAL_GET("rendering/scaling_3d/scale"); + viewport->set_scaling_3d_scale(scaling_3d_scale); + + const float fsr_sharpness = GLOBAL_GET("rendering/scaling_3d/fsr_sharpness"); + viewport->set_fsr_sharpness(fsr_sharpness); + + const float texture_mipmap_bias = GLOBAL_GET("rendering/textures/default_filters/texture_mipmap_bias"); + viewport->set_texture_mipmap_bias(texture_mipmap_bias); } void Node3DEditorViewport::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - EditorNode::get_singleton()->connect("project_settings_changed", callable_mp(this, &Node3DEditorViewport::_project_settings_changed)); - } + switch (p_what) { + case NOTIFICATION_READY: { + EditorNode::get_singleton()->connect("project_settings_changed", callable_mp(this, &Node3DEditorViewport::_project_settings_changed)); + } break; - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - bool visible = is_visible_in_tree(); + case NOTIFICATION_VISIBILITY_CHANGED: { + bool vp_visible = is_visible_in_tree(); - set_process(visible); + set_process(vp_visible); + set_physics_process(vp_visible); - if (visible) { - orthogonal = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL)); - _update_name(); - _update_camera(0); - } else { - set_freelook_active(false); - } - call_deferred(SNAME("update_transform_gizmo_view")); - rotation_control->set_visible(EditorSettings::get_singleton()->get("editors/3d/navigation/show_viewport_rotation_gizmo")); - } + if (vp_visible) { + orthogonal = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL)); + _update_name(); + _update_camera(0); + } else { + set_freelook_active(false); + } + call_deferred(SNAME("update_transform_gizmo_view")); + } break; - if (p_what == NOTIFICATION_RESIZED) { - call_deferred(SNAME("update_transform_gizmo_view")); - } + case NOTIFICATION_RESIZED: { + call_deferred(SNAME("update_transform_gizmo_view")); + } break; - if (p_what == NOTIFICATION_PROCESS) { - real_t delta = get_process_delta_time(); + case NOTIFICATION_PROCESS: { + real_t delta = get_process_delta_time(); - if (zoom_indicator_delay > 0) { - zoom_indicator_delay -= delta; - if (zoom_indicator_delay <= 0) { - surface->update(); - zoom_limit_label->hide(); + if (zoom_indicator_delay > 0) { + zoom_indicator_delay -= delta; + if (zoom_indicator_delay <= 0) { + surface->queue_redraw(); + zoom_limit_label->hide(); + } } - } - _update_freelook(delta); + _update_navigation_controls_visibility(); + _update_freelook(delta); - Node *scene_root = editor->get_scene_tree_dock()->get_editor_data()->get_edited_scene_root(); - if (previewing_cinema && scene_root != nullptr) { - Camera3D *cam = scene_root->get_viewport()->get_camera_3d(); - if (cam != nullptr && cam != previewing) { - //then switch the viewport's camera to the scene's viewport camera - if (previewing != nullptr) { - previewing->disconnect("tree_exited", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); + Node *scene_root = SceneTreeDock::get_singleton()->get_editor_data()->get_edited_scene_root(); + if (previewing_cinema && scene_root != nullptr) { + Camera3D *cam = scene_root->get_viewport()->get_camera_3d(); + if (cam != nullptr && cam != previewing) { + //then switch the viewport's camera to the scene's viewport camera + if (previewing != nullptr) { + previewing->disconnect("tree_exited", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); + } + previewing = cam; + previewing->connect("tree_exited", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); + RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), cam->get_camera()); + surface->queue_redraw(); } - previewing = cam; - previewing->connect("tree_exited", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); - RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), cam->get_camera()); - surface->update(); } - } - _update_camera(delta); + _update_camera(delta); - Map<Node *, Object *> &selection = editor_selection->get_selection(); + const HashMap<Node *, Object *> &selection = editor_selection->get_selection(); - bool changed = false; - bool exist = false; + bool changed = false; + bool exist = false; - for (const KeyValue<Node *, Object *> &E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E.key); - if (!sp) { - continue; - } + for (const KeyValue<Node *, Object *> &E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E.key); + if (!sp) { + continue; + } - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } - Transform3D t = sp->get_global_gizmo_transform(); - VisualInstance3D *vi = Object::cast_to<VisualInstance3D>(sp); - AABB new_aabb = vi ? vi->get_aabb() : _calculate_spatial_bounds(sp); + Transform3D t = sp->get_global_gizmo_transform(); + VisualInstance3D *vi = Object::cast_to<VisualInstance3D>(sp); + AABB new_aabb = vi ? vi->get_aabb() : _calculate_spatial_bounds(sp); - exist = true; - if (se->last_xform == t && se->aabb == new_aabb && !se->last_xform_dirty) { - continue; - } - changed = true; - se->last_xform_dirty = false; - se->last_xform = t; + exist = true; + if (se->last_xform == t && se->aabb == new_aabb && !se->last_xform_dirty) { + continue; + } + changed = true; + se->last_xform_dirty = false; + se->last_xform = t; - se->aabb = new_aabb; + se->aabb = new_aabb; - Transform3D t_offset = t; + Transform3D t_offset = t; - // apply AABB scaling before item's global transform - { - const Vector3 offset(0.005, 0.005, 0.005); - Basis aabb_s; - aabb_s.scale(se->aabb.size + offset); - t.translate(se->aabb.position - offset / 2); - t.basis = t.basis * aabb_s; - } - { - const Vector3 offset(0.01, 0.01, 0.01); - Basis aabb_s; - aabb_s.scale(se->aabb.size + offset); - t_offset.translate(se->aabb.position - offset / 2); - t_offset.basis = t_offset.basis * aabb_s; + // apply AABB scaling before item's global transform + { + const Vector3 offset(0.005, 0.005, 0.005); + Basis aabb_s; + aabb_s.scale(se->aabb.size + offset); + t.translate_local(se->aabb.position - offset / 2); + t.basis = t.basis * aabb_s; + } + { + const Vector3 offset(0.01, 0.01, 0.01); + Basis aabb_s; + aabb_s.scale(se->aabb.size + offset); + t_offset.translate_local(se->aabb.position - offset / 2); + t_offset.basis = t_offset.basis * aabb_s; + } + + RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance, t); + RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_offset, t_offset); + RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray, t); + RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray_offset, t_offset); } - RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance, t); - RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_offset, t_offset); - RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray, t); - RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray_offset, t_offset); - } + if (changed || (spatial_editor->is_gizmo_visible() && !exist)) { + spatial_editor->update_transform_gizmo(); + } - if (changed || (spatial_editor->is_gizmo_visible() && !exist)) { - spatial_editor->update_transform_gizmo(); - } + if (message_time > 0) { + if (message != last_message) { + surface->queue_redraw(); + last_message = message; + } - if (message_time > 0) { - if (message != last_message) { - surface->update(); - last_message = message; + message_time -= get_physics_process_delta_time(); + if (message_time < 0) { + surface->queue_redraw(); + } } - message_time -= get_physics_process_delta_time(); - if (message_time < 0) { - surface->update(); + bool show_info = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_INFORMATION)); + if (show_info != info_label->is_visible()) { + info_label->set_visible(show_info); } - } - bool show_info = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_INFORMATION)); - if (show_info != info_label->is_visible()) { - info_label->set_visible(show_info); - } + Camera3D *current_camera; - Camera3D *current_camera; + if (previewing) { + current_camera = previewing; + } else { + current_camera = camera; + } + + if (show_info) { + const String viewport_size = vformat(String::utf8("%d × %d"), viewport->get_size().x, viewport->get_size().y); + String text; + text += vformat(TTR("X: %s\n"), rtos(current_camera->get_position().x).pad_decimals(1)); + text += vformat(TTR("Y: %s\n"), rtos(current_camera->get_position().y).pad_decimals(1)); + text += vformat(TTR("Z: %s\n"), rtos(current_camera->get_position().z).pad_decimals(1)); + text += "\n"; + text += vformat( + TTR("Size: %s (%.1fMP)\n"), + viewport_size, + viewport->get_size().x * viewport->get_size().y * 0.000001); + + text += "\n"; + text += vformat(TTR("Objects: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_OBJECTS_IN_FRAME)); + text += vformat(TTR("Primitive Indices: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_PRIMITIVES_IN_FRAME)); + text += vformat(TTR("Draw Calls: %d"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_DRAW_CALLS_IN_FRAME)); + + info_label->set_text(text); + } + + // FPS Counter. + bool show_fps = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_FRAME_TIME)); + + if (show_fps != fps_label->is_visible()) { + cpu_time_label->set_visible(show_fps); + gpu_time_label->set_visible(show_fps); + fps_label->set_visible(show_fps); + RS::get_singleton()->viewport_set_measure_render_time(viewport->get_viewport_rid(), show_fps); + for (int i = 0; i < FRAME_TIME_HISTORY; i++) { + cpu_time_history[i] = 0; + gpu_time_history[i] = 0; + } + cpu_time_history_index = 0; + gpu_time_history_index = 0; + } + if (show_fps) { + cpu_time_history[cpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_cpu(viewport->get_viewport_rid()); + cpu_time_history_index = (cpu_time_history_index + 1) % FRAME_TIME_HISTORY; + double cpu_time = 0.0; + for (int i = 0; i < FRAME_TIME_HISTORY; i++) { + cpu_time += cpu_time_history[i]; + } + cpu_time /= FRAME_TIME_HISTORY; + // Prevent unrealistically low values. + cpu_time = MAX(0.01, cpu_time); + + gpu_time_history[gpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_gpu(viewport->get_viewport_rid()); + gpu_time_history_index = (gpu_time_history_index + 1) % FRAME_TIME_HISTORY; + double gpu_time = 0.0; + for (int i = 0; i < FRAME_TIME_HISTORY; i++) { + gpu_time += gpu_time_history[i]; + } + gpu_time /= FRAME_TIME_HISTORY; + // Prevent division by zero for the FPS counter (and unrealistically low values). + // This limits the reported FPS to 100000. + gpu_time = MAX(0.01, gpu_time); + + // Color labels depending on performance level ("good" = green, "OK" = yellow, "bad" = red). + // Middle point is at 15 ms. + cpu_time_label->set_text(vformat(TTR("CPU Time: %s ms"), rtos(cpu_time).pad_decimals(2))); + cpu_time_label->add_theme_color_override( + "font_color", + frame_time_gradient->get_color_at_offset( + Math::remap(cpu_time, 0, 30, 0, 1))); + + gpu_time_label->set_text(vformat(TTR("GPU Time: %s ms"), rtos(gpu_time).pad_decimals(2))); + // Middle point is at 15 ms. + gpu_time_label->add_theme_color_override( + "font_color", + frame_time_gradient->get_color_at_offset( + Math::remap(gpu_time, 0, 30, 0, 1))); + + const double fps = 1000.0 / gpu_time; + fps_label->set_text(vformat(TTR("FPS: %d"), fps)); + // Middle point is at 60 FPS. + fps_label->add_theme_color_override( + "font_color", + frame_time_gradient->get_color_at_offset( + Math::remap(fps, 110, 10, 0, 1))); + } + + bool show_cinema = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW)); + cinema_label->set_visible(show_cinema); + if (show_cinema) { + float cinema_half_width = cinema_label->get_size().width / 2.0f; + cinema_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -cinema_half_width); + } - if (previewing) { - current_camera = previewing; - } else { - current_camera = camera; - } - - if (show_info) { - const String viewport_size = vformat(String::utf8("%d × %d"), viewport->get_size().x, viewport->get_size().y); - String text; - text += vformat(TTR("X: %s\n"), rtos(current_camera->get_position().x).pad_decimals(1)); - text += vformat(TTR("Y: %s\n"), rtos(current_camera->get_position().y).pad_decimals(1)); - text += vformat(TTR("Z: %s\n"), rtos(current_camera->get_position().z).pad_decimals(1)); - text += "\n"; - text += vformat( - TTR("Size: %s (%.1fMP)\n"), - viewport_size, - viewport->get_size().x * viewport->get_size().y * 0.000001); - - text += "\n"; - text += vformat(TTR("Objects: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_OBJECTS_IN_FRAME)); - text += vformat(TTR("Primitives: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_PRIMITIVES_IN_FRAME)); - text += vformat(TTR("Draw Calls: %d"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_DRAW_CALLS_IN_FRAME)); - - info_label->set_text(text); - } - - // FPS Counter. - bool show_fps = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_FRAME_TIME)); - - if (show_fps != fps_label->is_visible()) { - cpu_time_label->set_visible(show_fps); - gpu_time_label->set_visible(show_fps); - fps_label->set_visible(show_fps); - RS::get_singleton()->viewport_set_measure_render_time(viewport->get_viewport_rid(), show_fps); - for (int i = 0; i < FRAME_TIME_HISTORY; i++) { - cpu_time_history[i] = 0; - gpu_time_history[i] = 0; - } - cpu_time_history_index = 0; - gpu_time_history_index = 0; - } - if (show_fps) { - cpu_time_history[cpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_cpu(viewport->get_viewport_rid()); - cpu_time_history_index = (cpu_time_history_index + 1) % FRAME_TIME_HISTORY; - double cpu_time = 0.0; - for (int i = 0; i < FRAME_TIME_HISTORY; i++) { - cpu_time += cpu_time_history[i]; - } - cpu_time /= FRAME_TIME_HISTORY; - // Prevent unrealistically low values. - cpu_time = MAX(0.01, cpu_time); - - gpu_time_history[gpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_gpu(viewport->get_viewport_rid()); - gpu_time_history_index = (gpu_time_history_index + 1) % FRAME_TIME_HISTORY; - double gpu_time = 0.0; - for (int i = 0; i < FRAME_TIME_HISTORY; i++) { - gpu_time += gpu_time_history[i]; - } - gpu_time /= FRAME_TIME_HISTORY; - // Prevent division by zero for the FPS counter (and unrealistically low values). - // This limits the reported FPS to 100000. - gpu_time = MAX(0.01, gpu_time); - - // Color labels depending on performance level ("good" = green, "OK" = yellow, "bad" = red). - // Middle point is at 15 ms. - cpu_time_label->set_text(vformat(TTR("CPU Time: %s ms"), rtos(cpu_time).pad_decimals(2))); - cpu_time_label->add_theme_color_override( - "font_color", - frame_time_gradient->get_color_at_offset( - Math::range_lerp(cpu_time, 0, 30, 0, 1))); - - gpu_time_label->set_text(vformat(TTR("GPU Time: %s ms"), rtos(gpu_time).pad_decimals(2))); - // Middle point is at 15 ms. - gpu_time_label->add_theme_color_override( - "font_color", - frame_time_gradient->get_color_at_offset( - Math::range_lerp(gpu_time, 0, 30, 0, 1))); - - const double fps = 1000.0 / gpu_time; - fps_label->set_text(vformat(TTR("FPS: %d"), fps)); - // Middle point is at 60 FPS. - fps_label->add_theme_color_override( - "font_color", - frame_time_gradient->get_color_at_offset( - Math::range_lerp(fps, 110, 10, 0, 1))); - } - - bool show_cinema = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW)); - cinema_label->set_visible(show_cinema); - if (show_cinema) { - float cinema_half_width = cinema_label->get_size().width / 2.0f; - cinema_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -cinema_half_width); - } - - if (lock_rotation) { - float locked_half_width = locked_label->get_size().width / 2.0f; - locked_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -locked_half_width); - } - } - - if (p_what == NOTIFICATION_ENTER_TREE) { - surface->connect("draw", callable_mp(this, &Node3DEditorViewport::_draw)); - surface->connect("gui_input", callable_mp(this, &Node3DEditorViewport::_sinput)); - surface->connect("mouse_entered", callable_mp(this, &Node3DEditorViewport::_surface_mouse_enter)); - surface->connect("mouse_exited", callable_mp(this, &Node3DEditorViewport::_surface_mouse_exit)); - surface->connect("focus_entered", callable_mp(this, &Node3DEditorViewport::_surface_focus_enter)); - surface->connect("focus_exited", callable_mp(this, &Node3DEditorViewport::_surface_focus_exit)); - - _init_gizmo_instance(index); - } - - if (p_what == NOTIFICATION_EXIT_TREE) { - _finish_gizmo_instances(); - } + if (lock_rotation) { + float locked_half_width = locked_label->get_size().width / 2.0f; + locked_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -locked_half_width); + } + } break; + + case NOTIFICATION_PHYSICS_PROCESS: { + if (!update_preview_node) { + return; + } + if (preview_node->is_inside_tree()) { + preview_node_pos = spatial_editor->snap_point(_get_instance_position(preview_node_viewport_pos)); + Transform3D preview_gl_transform = Transform3D(Basis(), preview_node_pos); + preview_node->set_global_transform(preview_gl_transform); + if (!preview_node->is_visible()) { + preview_node->show(); + } + } + update_preview_node = false; + } break; - if (p_what == NOTIFICATION_THEME_CHANGED) { - view_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); - preview_camera->set_icon(get_theme_icon(SNAME("Camera3D"), SNAME("EditorIcons"))); + case NOTIFICATION_ENTER_TREE: { + surface->connect("draw", callable_mp(this, &Node3DEditorViewport::_draw)); + surface->connect("gui_input", callable_mp(this, &Node3DEditorViewport::_sinput)); + surface->connect("mouse_entered", callable_mp(this, &Node3DEditorViewport::_surface_mouse_enter)); + surface->connect("mouse_exited", callable_mp(this, &Node3DEditorViewport::_surface_mouse_exit)); + surface->connect("focus_entered", callable_mp(this, &Node3DEditorViewport::_surface_focus_enter)); + surface->connect("focus_exited", callable_mp(this, &Node3DEditorViewport::_surface_focus_exit)); + + _init_gizmo_instance(index); + } break; - view_menu->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - view_menu->add_theme_style_override("hover", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - view_menu->add_theme_style_override("pressed", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - view_menu->add_theme_style_override("focus", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - view_menu->add_theme_style_override("disabled", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + case NOTIFICATION_EXIT_TREE: { + _finish_gizmo_instances(); + } break; - preview_camera->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - preview_camera->add_theme_style_override("hover", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - preview_camera->add_theme_style_override("pressed", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - preview_camera->add_theme_style_override("focus", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - preview_camera->add_theme_style_override("disabled", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - - frame_time_gradient->set_color(0, get_theme_color(SNAME("success_color"), SNAME("Editor"))); - frame_time_gradient->set_color(1, get_theme_color(SNAME("warning_color"), SNAME("Editor"))); - frame_time_gradient->set_color(2, get_theme_color(SNAME("error_color"), SNAME("Editor"))); + case NOTIFICATION_THEME_CHANGED: { + view_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + preview_camera->set_icon(get_theme_icon(SNAME("Camera3D"), SNAME("EditorIcons"))); + Control *gui_base = EditorNode::get_singleton()->get_gui_base(); + + view_menu->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + view_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + view_menu->add_theme_style_override("pressed", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + view_menu->add_theme_style_override("focus", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + view_menu->add_theme_style_override("disabled", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + + preview_camera->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + preview_camera->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + preview_camera->add_theme_style_override("pressed", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + preview_camera->add_theme_style_override("focus", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + preview_camera->add_theme_style_override("disabled", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + + frame_time_gradient->set_color(0, get_theme_color(SNAME("success_color"), SNAME("Editor"))); + frame_time_gradient->set_color(1, get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + frame_time_gradient->set_color(2, get_theme_color(SNAME("error_color"), SNAME("Editor"))); + + info_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + cpu_time_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + gpu_time_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + fps_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + cinema_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + locked_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + } break; - info_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - cpu_time_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - gpu_time_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - fps_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - cinema_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - locked_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - } + case NOTIFICATION_DRAG_END: { + // Clear preview material when dropped outside applicable object. + if (spatial_editor->get_preview_material().is_valid() && !is_drag_successful()) { + _remove_preview_material(); + } + } break; + } } -static void draw_indicator_bar(Control &surface, real_t fill, const Ref<Texture2D> icon, const Ref<Font> font, int font_size, const String &text) { +static void draw_indicator_bar(Control &p_surface, real_t p_fill, const Ref<Texture2D> p_icon, const Ref<Font> p_font, int p_font_size, const String &p_text, const Color &p_color) { // Adjust bar size from control height - const Vector2 surface_size = surface.get_size(); + const Vector2 surface_size = p_surface.get_size(); const real_t h = surface_size.y / 2.0; const real_t y = (surface_size.y - h) / 2.0; const Rect2 r(10 * EDSCALE, y, 6 * EDSCALE, h); - const real_t sy = r.size.y * fill; + const real_t sy = r.size.y * p_fill; // Note: because this bar appears over the viewport, it has to stay readable for any background color // Draw both neutral dark and bright colors to account this - surface.draw_rect(r, Color(1, 1, 1, 0.2)); - surface.draw_rect(Rect2(r.position.x, r.position.y + r.size.y - sy, r.size.x, sy), Color(1, 1, 1, 0.6)); - surface.draw_rect(r.grow(1), Color(0, 0, 0, 0.7), false, Math::round(EDSCALE)); + p_surface.draw_rect(r, p_color * Color(1, 1, 1, 0.2)); + p_surface.draw_rect(Rect2(r.position.x, r.position.y + r.size.y - sy, r.size.x, sy), p_color * Color(1, 1, 1, 0.6)); + p_surface.draw_rect(r.grow(1), Color(0, 0, 0, 0.7), false, Math::round(EDSCALE)); - const Vector2 icon_size = icon->get_size(); + const Vector2 icon_size = p_icon->get_size(); const Vector2 icon_pos = Vector2(r.position.x - (icon_size.x - r.size.x) / 2, r.position.y + r.size.y + 2 * EDSCALE); - surface.draw_texture(icon, icon_pos); + p_surface.draw_texture(p_icon, icon_pos, p_color); // Draw text below the bar (for speed/zoom information). - surface.draw_string(font, Vector2(icon_pos.x, icon_pos.y + icon_size.y + 16 * EDSCALE), text, HORIZONTAL_ALIGNMENT_LEFT, -1.f, font_size); + p_surface.draw_string_outline(p_font, Vector2(icon_pos.x, icon_pos.y + icon_size.y + 16 * EDSCALE), p_text, HORIZONTAL_ALIGNMENT_LEFT, -1.f, p_font_size, Math::round(2 * EDSCALE), Color(0, 0, 0)); + p_surface.draw_string(p_font, Vector2(icon_pos.x, icon_pos.y + icon_size.y + 16 * EDSCALE), p_text, HORIZONTAL_ALIGNMENT_LEFT, -1.f, p_font_size, p_color); } void Node3DEditorViewport::_draw() { EditorPluginList *over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_over(); if (!over_plugin_list->is_empty()) { - over_plugin_list->forward_spatial_draw_over_viewport(surface); + over_plugin_list->forward_3d_draw_over_viewport(surface); } - EditorPluginList *force_over_plugin_list = editor->get_editor_plugins_force_over(); + EditorPluginList *force_over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_force_over(); if (!force_over_plugin_list->is_empty()) { - force_over_plugin_list->forward_spatial_force_draw_over_viewport(surface); + force_over_plugin_list->forward_3d_force_draw_over_viewport(surface); } if (surface->has_focus()) { @@ -3033,7 +2991,7 @@ void Node3DEditorViewport::_draw() { font->draw_string(ci, msgpos, message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 1)); } - if (_edit.mode == TRANSFORM_ROTATE) { + if (_edit.mode == TRANSFORM_ROTATE && _edit.show_rotation_line) { Point2 center = _point_to_screen(_edit.center); Color handle_color; @@ -3061,7 +3019,7 @@ void Node3DEditorViewport::_draw() { Math::round(2 * EDSCALE)); } if (previewing) { - Size2 ss = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height")); + Size2 ss = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); float aspect = ss.aspect(); Size2 s = get_size(); @@ -3096,7 +3054,7 @@ void Node3DEditorViewport::_draw() { real_t scale_length = (max_speed - min_speed); if (!Math::is_zero_approx(scale_length)) { - real_t logscale_t = 1.0 - Math::log(1 + freelook_speed - min_speed) / Math::log(1 + scale_length); + real_t logscale_t = 1.0 - Math::log1p(freelook_speed - min_speed) / Math::log1p(scale_length); // Display the freelook speed to help the user get a better sense of scale. const int precision = freelook_speed < 1.0 ? 2 : 1; @@ -3106,7 +3064,8 @@ void Node3DEditorViewport::_draw() { get_theme_icon(SNAME("ViewportSpeed"), SNAME("EditorIcons")), get_theme_font(SNAME("font"), SNAME("Label")), get_theme_font_size(SNAME("font_size"), SNAME("Label")), - vformat("%s u/s", String::num(freelook_speed).pad_decimals(precision))); + vformat("%s u/s", String::num(freelook_speed).pad_decimals(precision)), + Color(1.0, 0.95, 0.7)); } } else { @@ -3118,7 +3077,7 @@ void Node3DEditorViewport::_draw() { real_t scale_length = (max_distance - min_distance); if (!Math::is_zero_approx(scale_length)) { - real_t logscale_t = 1.0 - Math::log(1 + cursor.distance - min_distance) / Math::log(1 + scale_length); + real_t logscale_t = 1.0 - Math::log1p(cursor.distance - min_distance) / Math::log1p(scale_length); // Display the zoom center distance to help the user get a better sense of scale. const int precision = cursor.distance < 1.0 ? 2 : 1; @@ -3128,7 +3087,8 @@ void Node3DEditorViewport::_draw() { get_theme_icon(SNAME("ViewportZoom"), SNAME("EditorIcons")), get_theme_font(SNAME("font"), SNAME("Label")), get_theme_font_size(SNAME("font_size"), SNAME("Label")), - vformat("%s u", String::num(cursor.distance).pad_decimals(precision))); + vformat("%s u", String::num(cursor.distance).pad_decimals(precision)), + Color(0.7, 0.95, 1.0)); } } } @@ -3136,6 +3096,7 @@ void Node3DEditorViewport::_draw() { } void Node3DEditorViewport::_menu_option(int p_option) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); switch (p_option) { case VIEW_TOP: { cursor.y_rot = 0; @@ -3224,12 +3185,19 @@ void Node3DEditorViewport::_menu_option(int p_option) { Transform3D xform; if (orthogonal) { xform = sp->get_global_transform(); - xform.basis.set_euler(camera_transform.basis.get_euler()); + xform.basis = Basis::from_euler(camera_transform.basis.get_euler()); } else { xform = camera_transform; xform.scale_basis(sp->get_scale()); } + if (Object::cast_to<Decal>(E)) { + // Adjust rotation to match Decal's default orientation. + // This makes the decal "look" in the same direction as the camera, + // rather than pointing down relative to the camera orientation. + xform.basis.rotate_local(Vector3(1, 0, 0), Math_TAU * 0.25); + } + undo_redo->add_do_method(sp, "set_global_transform", xform); undo_redo->add_undo_method(sp, "set_global_transform", sp->get_global_gizmo_transform()); } @@ -3257,7 +3225,16 @@ void Node3DEditorViewport::_menu_option(int p_option) { continue; } - undo_redo->add_do_method(sp, "set_rotation", camera_transform.basis.get_euler_normalized()); + Basis basis = camera_transform.basis; + + if (Object::cast_to<Decal>(E)) { + // Adjust rotation to match Decal's default orientation. + // This makes the decal "look" in the same direction as the camera, + // rather than pointing down relative to the camera orientation. + basis.rotate_local(Vector3(1, 0, 0), Math_TAU * 0.25); + } + + undo_redo->add_do_method(sp, "set_rotation", basis.get_euler_normalized()); undo_redo->add_undo_method(sp, "set_rotation", sp->get_rotation()); } undo_redo->commit_action(); @@ -3268,7 +3245,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { bool current = view_menu->get_popup()->is_item_checked(idx); current = !current; if (current) { - camera->set_environment(RES()); + camera->set_environment(Ref<Resource>()); } else { camera->set_environment(Node3DEditor::get_singleton()->get_viewport_environment()); } @@ -3294,6 +3271,10 @@ void Node3DEditorViewport::_menu_option(int p_option) { _update_name(); } break; + case VIEW_SWITCH_PERSPECTIVE_ORTHOGONAL: { + _menu_option(orthogonal ? VIEW_PERSPECTIVE : VIEW_ORTHOGONAL); + + } break; case VIEW_AUTO_ORTHOGONAL: { int idx = view_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL); bool current = view_menu->get_popup()->is_item_checked(idx); @@ -3363,8 +3344,8 @@ void Node3DEditorViewport::_menu_option(int p_option) { case VIEW_HALF_RESOLUTION: { int idx = view_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION); bool current = view_menu->get_popup()->is_item_checked(idx); - current = !current; - view_menu->get_popup()->set_item_checked(idx, current); + view_menu->get_popup()->set_item_checked(idx, !current); + _update_shrink(); } break; case VIEW_INFORMATION: { int idx = view_menu->get_popup()->get_item_index(VIEW_INFORMATION); @@ -3402,7 +3383,8 @@ void Node3DEditorViewport::_menu_option(int p_option) { case VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS: case VIEW_DISPLAY_DEBUG_CLUSTER_DECALS: case VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES: - case VIEW_DISPLAY_DEBUG_OCCLUDERS: { + case VIEW_DISPLAY_DEBUG_OCCLUDERS: + case VIEW_DISPLAY_MOTION_VECTORS: { static const int display_options[] = { VIEW_DISPLAY_NORMAL, VIEW_DISPLAY_WIREFRAME, @@ -3410,7 +3392,6 @@ void Node3DEditorViewport::_menu_option(int p_option) { VIEW_DISPLAY_SHADELESS, VIEW_DISPLAY_LIGHTING, VIEW_DISPLAY_NORMAL_BUFFER, - VIEW_DISPLAY_WIREFRAME, VIEW_DISPLAY_DEBUG_SHADOW_ATLAS, VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS, VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO, @@ -3430,6 +3411,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { VIEW_DISPLAY_DEBUG_CLUSTER_DECALS, VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES, VIEW_DISPLAY_DEBUG_OCCLUDERS, + VIEW_DISPLAY_MOTION_VECTORS, VIEW_MAX }; static const Viewport::DebugDraw debug_draw_modes[] = { @@ -3439,7 +3421,6 @@ void Node3DEditorViewport::_menu_option(int p_option) { Viewport::DEBUG_DRAW_UNSHADED, Viewport::DEBUG_DRAW_LIGHTING, Viewport::DEBUG_DRAW_NORMAL_BUFFER, - Viewport::DEBUG_DRAW_WIREFRAME, Viewport::DEBUG_DRAW_SHADOW_ATLAS, Viewport::DEBUG_DRAW_DIRECTIONAL_SHADOW_ATLAS, Viewport::DEBUG_DRAW_VOXEL_GI_ALBEDO, @@ -3459,11 +3440,10 @@ void Node3DEditorViewport::_menu_option(int p_option) { Viewport::DEBUG_DRAW_CLUSTER_DECALS, Viewport::DEBUG_DRAW_CLUSTER_REFLECTION_PROBES, Viewport::DEBUG_DRAW_OCCLUDERS, + Viewport::DEBUG_DRAW_MOTION_VECTORS, }; - int idx = 0; - - while (display_options[idx] != VIEW_MAX) { + for (int idx = 0; display_options[idx] != VIEW_MAX; idx++) { int id = display_options[idx]; int item_idx = view_menu->get_popup()->get_item_index(id); if (item_idx != -1) { @@ -3477,7 +3457,6 @@ void Node3DEditorViewport::_menu_option(int p_option) { if (id == p_option) { viewport->set_debug_draw(debug_draw_modes[idx]); } - idx++; } } break; } @@ -3509,6 +3488,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(move_gizmo_instance[i], layer); RS::get_singleton()->instance_geometry_set_flag(move_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(move_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); move_plane_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(move_plane_gizmo_instance[i], spatial_editor->get_move_plane_gizmo(i)->get_rid()); @@ -3517,6 +3497,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(move_plane_gizmo_instance[i], layer); RS::get_singleton()->instance_geometry_set_flag(move_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(move_plane_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); rotate_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(rotate_gizmo_instance[i], spatial_editor->get_rotate_gizmo(i)->get_rid()); @@ -3525,6 +3506,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[i], layer); RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); scale_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(scale_gizmo_instance[i], spatial_editor->get_scale_gizmo(i)->get_rid()); @@ -3533,6 +3515,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(scale_gizmo_instance[i], layer); RS::get_singleton()->instance_geometry_set_flag(scale_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(scale_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); scale_plane_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(scale_plane_gizmo_instance[i], spatial_editor->get_scale_plane_gizmo(i)->get_rid()); @@ -3541,6 +3524,16 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(scale_plane_gizmo_instance[i], layer); RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); + + axis_gizmo_instance[i] = RS::get_singleton()->instance_create(); + RS::get_singleton()->instance_set_base(axis_gizmo_instance[i], spatial_editor->get_axis_gizmo(i)->get_rid()); + RS::get_singleton()->instance_set_scenario(axis_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario()); + RS::get_singleton()->instance_set_visible(axis_gizmo_instance[i], true); + RS::get_singleton()->instance_geometry_set_cast_shadows_setting(axis_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_set_layer_mask(axis_gizmo_instance[i], layer); + RS::get_singleton()->instance_geometry_set_flag(axis_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(axis_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); } // Rotation white outline @@ -3551,15 +3544,18 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[3], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[3], layer); RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[3], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[3], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); } void Node3DEditorViewport::_finish_gizmo_instances() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); for (int i = 0; i < 3; i++) { RS::get_singleton()->free(move_gizmo_instance[i]); RS::get_singleton()->free(move_plane_gizmo_instance[i]); RS::get_singleton()->free(rotate_gizmo_instance[i]); RS::get_singleton()->free(scale_gizmo_instance[i]); RS::get_singleton()->free(scale_plane_gizmo_instance[i]); + RS::get_singleton()->free(axis_gizmo_instance[i]); } // Rotation white outline RS::get_singleton()->free(rotate_gizmo_instance[3]); @@ -3569,7 +3565,8 @@ void Node3DEditorViewport::_toggle_camera_preview(bool p_activate) { ERR_FAIL_COND(p_activate && !preview); ERR_FAIL_COND(!p_activate && !previewing); - rotation_control->set_visible(!p_activate); + previewing_camera = p_activate; + _update_navigation_controls_visibility(); if (!p_activate) { previewing->disconnect("tree_exiting", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); @@ -3578,19 +3575,19 @@ void Node3DEditorViewport::_toggle_camera_preview(bool p_activate) { if (!preview) { preview_camera->hide(); } - surface->update(); + surface->queue_redraw(); } else { previewing = preview; previewing->connect("tree_exiting", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), preview->get_camera()); //replace - surface->update(); + surface->queue_redraw(); } } void Node3DEditorViewport::_toggle_cinema_preview(bool p_activate) { previewing_cinema = p_activate; - rotation_control->set_visible(!p_activate); + _update_navigation_controls_visibility(); if (!previewing_cinema) { if (previewing != nullptr) { @@ -3606,7 +3603,7 @@ void Node3DEditorViewport::_toggle_cinema_preview(bool p_activate) { preview_camera->show(); } view_menu->show(); - surface->update(); + surface->queue_redraw(); } } @@ -3652,21 +3649,22 @@ void Node3DEditorViewport::update_transform_gizmo_view() { RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false); RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false); RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false); + RenderingServer::get_singleton()->instance_set_visible(axis_gizmo_instance[i], false); } // Rotation white outline RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], false); return; } - const Vector3 camz = -camera_xform.get_basis().get_axis(2).normalized(); - const Vector3 camy = -camera_xform.get_basis().get_axis(1).normalized(); + const Vector3 camz = -camera_xform.get_basis().get_column(2).normalized(); + const Vector3 camy = -camera_xform.get_basis().get_column(1).normalized(); const Plane p = Plane(camz, camera_xform.origin); const real_t gizmo_d = MAX(Math::abs(p.distance_to(xform.origin)), CMP_EPSILON); const real_t d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y; const real_t d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y; const real_t dd = MAX(Math::abs(d0 - d1), CMP_EPSILON); - const real_t gizmo_size = EditorSettings::get_singleton()->get("editors/3d/manipulator_gizmo_size"); + const real_t gizmo_size = EDITOR_GET("editors/3d/manipulator_gizmo_size"); // At low viewport heights, multiply the gizmo scale based on the viewport height. // This prevents the gizmo from growing very large and going outside the viewport. const int viewport_base_height = 400 * MAX(1, EDSCALE); @@ -3676,8 +3674,6 @@ void Node3DEditorViewport::update_transform_gizmo_view() { subviewport_container->get_stretch_shrink(); Vector3 scale = Vector3(1, 1, 1) * gizmo_scale; - xform.basis.scale(scale); - // if the determinant is zero, we should disable the gizmo from being rendered // this prevents supplying bad values to the renderer and then having to filter it out again if (xform.basis.determinant() == 0) { @@ -3694,18 +3690,34 @@ void Node3DEditorViewport::update_transform_gizmo_view() { } for (int i = 0; i < 3; i++) { - RenderingServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], xform); + Transform3D axis_angle; + if (xform.basis.get_column(i).normalized().dot(xform.basis.get_column((i + 1) % 3).normalized()) < 1.0) { + axis_angle = axis_angle.looking_at(xform.basis.get_column(i).normalized(), xform.basis.get_column((i + 1) % 3).normalized()); + } + axis_angle.basis.scale(scale); + axis_angle.origin = xform.origin; + RenderingServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], axis_angle); RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE)); - RenderingServer::get_singleton()->instance_set_transform(move_plane_gizmo_instance[i], xform); + RenderingServer::get_singleton()->instance_set_transform(move_plane_gizmo_instance[i], axis_angle); RenderingServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE)); - RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[i], xform); + RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[i], axis_angle); RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE)); - RenderingServer::get_singleton()->instance_set_transform(scale_gizmo_instance[i], xform); + RenderingServer::get_singleton()->instance_set_transform(scale_gizmo_instance[i], axis_angle); RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE)); - RenderingServer::get_singleton()->instance_set_transform(scale_plane_gizmo_instance[i], xform); + RenderingServer::get_singleton()->instance_set_transform(scale_plane_gizmo_instance[i], axis_angle); RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE)); + RenderingServer::get_singleton()->instance_set_transform(axis_gizmo_instance[i], xform); } + + bool show_axes = spatial_editor->is_gizmo_visible() && _edit.mode != TRANSFORM_NONE; + RenderingServer *rs = RenderingServer::get_singleton(); + rs->instance_set_visible(axis_gizmo_instance[0], show_axes && (_edit.plane == TRANSFORM_X_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_XZ)); + rs->instance_set_visible(axis_gizmo_instance[1], show_axes && (_edit.plane == TRANSFORM_Y_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_YZ)); + rs->instance_set_visible(axis_gizmo_instance[2], show_axes && (_edit.plane == TRANSFORM_Z_AXIS || _edit.plane == TRANSFORM_XZ || _edit.plane == TRANSFORM_YZ)); + // Rotation white outline + xform.orthonormalize(); + xform.basis.scale(scale); RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform); RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE)); } @@ -3826,7 +3838,7 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) { previewing = Object::cast_to<Camera3D>(pv); previewing->connect("tree_exiting", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), previewing->get_camera()); //replace - surface->update(); + surface->queue_redraw(); preview_camera->set_pressed(true); preview_camera->show(); } @@ -3873,8 +3885,6 @@ Dictionary Node3DEditorViewport::get_state() const { void Node3DEditorViewport::_bind_methods() { ClassDB::bind_method(D_METHOD("update_transform_gizmo_view"), &Node3DEditorViewport::update_transform_gizmo_view); // Used by call_deferred. - ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &Node3DEditorViewport::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("_drop_data_fw"), &Node3DEditorViewport::drop_data_fw); ADD_SIGNAL(MethodInfo("toggle_maximize_view", PropertyInfo(Variant::OBJECT, "viewport"))); ADD_SIGNAL(MethodInfo("clicked", PropertyInfo(Variant::OBJECT, "viewport"))); @@ -3897,7 +3907,7 @@ void Node3DEditorViewport::focus_selection() { Vector3 center; int count = 0; - List<Node *> &selection = editor_selection->get_selected_node_list(); + const List<Node *> &selection = editor_selection->get_selected_node_list(); for (Node *E : selection) { Node3D *sp = Object::cast_to<Node3D>(E); @@ -3936,24 +3946,45 @@ void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node, Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const { const float MAX_DISTANCE = 50.0; + const float FALLBACK_DISTANCE = 5.0; Vector3 world_ray = _get_ray(p_pos); Vector3 world_pos = _get_ray_pos(p_pos); - Vector3 point = world_pos + world_ray * MAX_DISTANCE; - PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state(); PhysicsDirectSpaceState3D::RayParameters ray_params; ray_params.from = world_pos; - ray_params.to = world_pos + world_ray * MAX_DISTANCE; + ray_params.to = world_pos + world_ray * camera->get_far(); PhysicsDirectSpaceState3D::RayResult result; if (ss->intersect_ray(ray_params, result)) { - point = result.position; + return result.position; + } + + const bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL; + + // The XZ plane. + Vector3 intersection; + Plane plane(Vector3(0, 1, 0)); + if (plane.intersects_ray(world_pos, world_ray, &intersection)) { + if (is_orthogonal || world_pos.distance_to(intersection) <= MAX_DISTANCE) { + return intersection; + } } - return point; + // Plane facing the camera using fallback distance. + if (is_orthogonal) { + plane = Plane(world_ray, cursor.pos - world_ray * (cursor.distance - FALLBACK_DISTANCE)); + } else { + plane = Plane(world_ray, world_pos + world_ray * FALLBACK_DISTANCE); + } + if (plane.intersects_ray(world_pos, world_ray, &intersection)) { + return intersection; + } + + // Not likely, but just in case... + return world_pos + world_ray * FALLBACK_DISTANCE; } AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, bool p_exclude_top_level_transform) { @@ -3969,7 +4000,7 @@ AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, boo if (child) { AABB child_bounds = _calculate_spatial_bounds(child, false); - if (bounds.size == Vector3() && p_parent->get_class_name() == StringName("Node3D")) { + if (bounds.size == Vector3() && p_parent) { bounds = child_bounds; } else { bounds.merge_with(child_bounds); @@ -3977,7 +4008,7 @@ AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, boo } } - if (bounds.size == Vector3() && p_parent->get_class_name() != StringName("Node3D")) { + if (bounds.size == Vector3() && !p_parent) { bounds = AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4)); } @@ -4019,10 +4050,10 @@ Node *Node3DEditorViewport::_sanitize_preview_node(Node *p_node) const { return p_node; } -void Node3DEditorViewport::_create_preview(const Vector<String> &files) const { +void Node3DEditorViewport::_create_preview_node(const Vector<String> &files) const { for (int i = 0; i < files.size(); i++) { String path = files[i]; - RES res = ResourceLoader::load(path); + Ref<Resource> res = ResourceLoader::load(path); ERR_CONTINUE(res.is_null()); Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res)); @@ -4040,21 +4071,121 @@ void Node3DEditorViewport::_create_preview(const Vector<String> &files) const { } } } - editor->get_scene_root()->add_child(preview_node); + EditorNode::get_singleton()->get_scene_root()->add_child(preview_node); } } *preview_bounds = _calculate_spatial_bounds(preview_node); } -void Node3DEditorViewport::_remove_preview() { +void Node3DEditorViewport::_remove_preview_node() { if (preview_node->get_parent()) { for (int i = preview_node->get_child_count() - 1; i >= 0; i--) { Node *node = preview_node->get_child(i); - node->queue_delete(); + node->queue_free(); preview_node->remove_child(node); } - editor->get_scene_root()->remove_child(preview_node); + EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node); + } +} + +bool Node3DEditorViewport::_apply_preview_material(ObjectID p_target, const Point2 &p_point) const { + _reset_preview_material(); + + if (p_target.is_null()) { + return false; + } + + spatial_editor->set_preview_material_target(p_target); + + Object *target_inst = ObjectDB::get_instance(p_target); + + bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); + + MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(target_inst); + if (is_ctrl && mesh_instance) { + Ref<Mesh> mesh = mesh_instance->get_mesh(); + int surface_count = mesh->get_surface_count(); + + Vector3 world_ray = _get_ray(p_point); + Vector3 world_pos = _get_ray_pos(p_point); + + int closest_surface = -1; + float closest_dist = 1e20; + + Transform3D gt = mesh_instance->get_global_transform(); + + Transform3D ai = gt.affine_inverse(); + Vector3 xform_ray = ai.basis.xform(world_ray).normalized(); + Vector3 xform_pos = ai.xform(world_pos); + + for (int surface_idx = 0; surface_idx < surface_count; surface_idx++) { + Ref<TriangleMesh> surface_mesh = mesh->generate_surface_triangle_mesh(surface_idx); + + Vector3 rpos, rnorm; + if (surface_mesh->intersect_ray(xform_pos, xform_ray, rpos, rnorm)) { + Vector3 hitpos = gt.xform(rpos); + + const real_t dist = world_pos.distance_to(hitpos); + + if (dist < 0) { + continue; + } + + if (dist < closest_dist) { + closest_surface = surface_idx; + closest_dist = dist; + } + } + } + + if (closest_surface == -1) { + return false; + } + + if (spatial_editor->get_preview_material() != mesh_instance->get_surface_override_material(closest_surface)) { + spatial_editor->set_preview_material_surface(closest_surface); + spatial_editor->set_preview_reset_material(mesh_instance->get_surface_override_material(closest_surface)); + mesh_instance->set_surface_override_material(closest_surface, spatial_editor->get_preview_material()); + } + + return true; + } + + GeometryInstance3D *geometry_instance = Object::cast_to<GeometryInstance3D>(target_inst); + if (geometry_instance && spatial_editor->get_preview_material() != geometry_instance->get_material_override()) { + spatial_editor->set_preview_reset_material(geometry_instance->get_material_override()); + geometry_instance->set_material_override(spatial_editor->get_preview_material()); + return true; } + + return false; +} + +void Node3DEditorViewport::_reset_preview_material() const { + ObjectID last_target = spatial_editor->get_preview_material_target(); + if (last_target.is_null()) { + return; + } + Object *last_target_inst = ObjectDB::get_instance(last_target); + + MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(last_target_inst); + GeometryInstance3D *geometry_instance = Object::cast_to<GeometryInstance3D>(last_target_inst); + if (mesh_instance && spatial_editor->get_preview_material_surface() != -1) { + mesh_instance->set_surface_override_material(spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_reset_material()); + spatial_editor->set_preview_material_surface(-1); + } else if (geometry_instance) { + geometry_instance->set_material_override(spatial_editor->get_preview_reset_material()); + } +} + +void Node3DEditorViewport::_remove_preview_material() { + preview_material_label->hide(); + preview_material_label_desc->hide(); + + spatial_editor->set_preview_material(Ref<Material>()); + spatial_editor->set_preview_reset_material(Ref<Material>()); + spatial_editor->set_preview_material_target(ObjectID()); + spatial_editor->set_preview_material_surface(-1); } bool Node3DEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) { @@ -4073,7 +4204,7 @@ bool Node3DEditorViewport::_cyclical_dependency_exists(const String &p_target_sc } bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) { - RES res = ResourceLoader::load(path); + Ref<Resource> res = ResourceLoader::load(path); ERR_FAIL_COND_V(res.is_null(), false); Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); @@ -4088,19 +4219,7 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. String name = path.get_file().get_basename(); - switch (ProjectSettings::get_singleton()->get("editor/node_naming/name_casing").operator int()) { - case NAME_CASING_PASCAL_CASE: - name = name.capitalize().replace(" ", ""); - break; - case NAME_CASING_CAMEL_CASE: - name = name.capitalize().replace(" ", ""); - name[0] = name.to_lower()[0]; - break; - case NAME_CASING_SNAKE_CASE: - name = name.capitalize().replace(" ", "_").to_lower(); - break; - } - mesh_instance->set_name(name); + mesh_instance->set_name(Node::adjust_name_casing(name)); instantiated_scene = mesh_instance; } else { @@ -4116,8 +4235,8 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po return false; } - if (!editor->get_edited_scene()->get_scene_file_path().is_empty()) { // cyclical instancing - if (_cyclical_dependency_exists(editor->get_edited_scene()->get_scene_file_path(), instantiated_scene)) { + if (!EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path().is_empty()) { // Cyclic instantiation. + if (_cyclical_dependency_exists(EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path(), instantiated_scene)) { memdelete(instantiated_scene); return false; } @@ -4127,43 +4246,64 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path)); } - editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene); - editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", editor->get_edited_scene()); - editor_data->get_undo_redo().add_do_reference(instantiated_scene); - editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instantiated_scene); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->add_do_method(parent, "add_child", instantiated_scene, true); + undo_redo->add_do_method(instantiated_scene, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_reference(instantiated_scene); + undo_redo->add_undo_method(parent, "remove_child", instantiated_scene); String new_name = parent->validate_child_name(instantiated_scene); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); - editor_data->get_undo_redo().add_do_method(ed, "live_debug_instance_node", editor->get_edited_scene()->get_path_to(parent), path, new_name); - editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + undo_redo->add_do_method(ed, "live_debug_instantiate_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent), path, new_name); + undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); Node3D *node3d = Object::cast_to<Node3D>(instantiated_scene); if (node3d) { - Transform3D global_transform; + Transform3D gl_transform; Node3D *parent_node3d = Object::cast_to<Node3D>(parent); if (parent_node3d) { - global_transform = parent_node3d->get_global_gizmo_transform(); + gl_transform = parent_node3d->get_global_gizmo_transform(); } - global_transform.origin = spatial_editor->snap_point(_get_instance_position(p_point)); - global_transform.basis *= node3d->get_transform().basis; + gl_transform.origin = preview_node_pos; + gl_transform.basis *= node3d->get_transform().basis; - editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_global_transform", global_transform); + undo_redo->add_do_method(instantiated_scene, "set_global_transform", gl_transform); } return true; } void Node3DEditorViewport::_perform_drop_data() { - _remove_preview(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + if (spatial_editor->get_preview_material_target().is_valid()) { + GeometryInstance3D *geometry_instance = Object::cast_to<GeometryInstance3D>(ObjectDB::get_instance(spatial_editor->get_preview_material_target())); + MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(ObjectDB::get_instance(spatial_editor->get_preview_material_target())); + if (mesh_instance && spatial_editor->get_preview_material_surface() != -1) { + undo_redo->create_action(vformat(TTR("Set Surface %d Override Material"), spatial_editor->get_preview_material_surface())); + undo_redo->add_do_method(geometry_instance, "set_surface_override_material", spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_material()); + undo_redo->add_undo_method(geometry_instance, "set_surface_override_material", spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_reset_material()); + undo_redo->commit_action(); + } else if (geometry_instance) { + undo_redo->create_action(TTR("Set Material Override")); + undo_redo->add_do_method(geometry_instance, "set_material_override", spatial_editor->get_preview_material()); + undo_redo->add_undo_method(geometry_instance, "set_material_override", spatial_editor->get_preview_reset_material()); + undo_redo->commit_action(); + } + + _remove_preview_material(); + return; + } + + _remove_preview_node(); Vector<String> error_files; - editor_data->get_undo_redo().create_action(TTR("Create Node")); + undo_redo->create_action(TTR("Create Node")); for (int i = 0; i < selected_files.size(); i++) { String path = selected_files[i]; - RES res = ResourceLoader::load(path); + Ref<Resource> res = ResourceLoader::load(path); if (res.is_null()) { continue; } @@ -4177,7 +4317,7 @@ void Node3DEditorViewport::_perform_drop_data() { } } - editor_data->get_undo_redo().commit_action(); + undo_redo->commit_action(); if (error_files.size() > 0) { String files_str; @@ -4185,15 +4325,17 @@ void Node3DEditorViewport::_perform_drop_data() { files_str += error_files[i].get_file().get_basename() + ","; } files_str = files_str.substr(0, files_str.length() - 1); - accept->set_text(vformat(TTR("Error instancing scene from %s"), files_str.get_data())); + accept->set_text(vformat(TTR("Error instantiating scene from %s"), files_str.get_data())); accept->popup_centered(); } } -bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { +bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + preview_node_viewport_pos = p_point; + bool can_instantiate = false; - if (!preview_node->is_inside_tree()) { + if (!preview_node->is_inside_tree() && spatial_editor->get_preview_material().is_null()) { Dictionary d = p_data; if (d.has("type") && (String(d["type"]) == "files")) { Vector<String> files = d["files"]; @@ -4202,27 +4344,51 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant ResourceLoader::get_recognized_extensions_for_type("PackedScene", &scene_extensions); List<String> mesh_extensions; ResourceLoader::get_recognized_extensions_for_type("Mesh", &mesh_extensions); + List<String> material_extensions; + ResourceLoader::get_recognized_extensions_for_type("Material", &material_extensions); + List<String> texture_extensions; + ResourceLoader::get_recognized_extensions_for_type("Texture", &texture_extensions); for (int i = 0; i < files.size(); i++) { - if (mesh_extensions.find(files[i].get_extension()) || scene_extensions.find(files[i].get_extension())) { - RES res = ResourceLoader::load(files[i]); + String extension = files[i].get_extension().to_lower(); + + // Check if dragged files with mesh or scene extension can be created at least once. + if (mesh_extensions.find(extension) || + scene_extensions.find(extension) || + material_extensions.find(extension) || + texture_extensions.find(extension)) { + Ref<Resource> res = ResourceLoader::load(files[i]); if (res.is_null()) { continue; } - - String type = res->get_class(); - if (type == "PackedScene") { - Ref<PackedScene> sdata = ResourceLoader::load(files[i]); - Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + Ref<PackedScene> scn = res; + Ref<Mesh> mesh = res; + Ref<Material> mat = res; + Ref<Texture2D> tex = res; + if (scn.is_valid()) { + Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); if (!instantiated_scene) { continue; } memdelete(instantiated_scene); - } else if (ClassDB::is_parent_class(type, "Mesh")) { - Ref<Mesh> mesh = ResourceLoader::load(files[i]); - if (!mesh.is_valid()) { - continue; + } else if (mat.is_valid()) { + Ref<BaseMaterial3D> base_mat = res; + Ref<ShaderMaterial> shader_mat = res; + + if (base_mat.is_null() && !shader_mat.is_null()) { + break; } + + spatial_editor->set_preview_material(mat); + break; + } else if (mesh.is_valid()) { + // Let the mesh pass. + } else if (tex.is_valid()) { + Ref<StandardMaterial3D> new_mat = memnew(StandardMaterial3D); + new_mat->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, tex); + + spatial_editor->set_preview_material(new_mat); + break; } else { continue; } @@ -4231,19 +4397,30 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant } } if (can_instantiate) { - _create_preview(files); + _create_preview_node(files); + preview_node->hide(); } } } else { - can_instantiate = true; + if (preview_node->is_inside_tree()) { + can_instantiate = true; + } } if (can_instantiate) { - Transform3D global_transform = Transform3D(Basis(), _get_instance_position(p_point)); - preview_node->set_global_transform(global_transform); + update_preview_node = true; + return true; } - return can_instantiate; + if (spatial_editor->get_preview_material().is_valid()) { + preview_material_label->show(); + preview_material_label_desc->show(); + + ObjectID new_preview_material_target = _select_ray(p_point); + return _apply_preview_material(new_preview_material_target, p_point); + } + + return false; } void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { @@ -4260,8 +4437,8 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_ selected_files = d["files"]; } - List<Node *> selected_nodes = editor->get_editor_selection()->get_selected_node_list(); - Node *root_node = editor->get_edited_scene(); + List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list(); + Node *root_node = EditorNode::get_singleton()->get_edited_scene(); if (selected_nodes.size() == 1) { Node *selected_node = selected_nodes[0]; target_node = root_node; @@ -4275,13 +4452,13 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_ target_node = root_node; } else { // Create a root node so we can add child nodes to it. - EditorNode::get_singleton()->get_scene_tree_dock()->add_root_node(memnew(Node3D)); + SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D)); target_node = get_tree()->get_edited_scene_root(); } } else { accept->set_text(TTR("Cannot drag and drop into multiple selected nodes.")); accept->popup_centered(); - _remove_preview(); + _remove_preview_node(); return; } @@ -4290,20 +4467,435 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_ _perform_drop_data(); } -Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, EditorNode *p_editor, int p_index) { +void Node3DEditorViewport::begin_transform(TransformMode p_mode, bool instant) { + if (get_selected_count() > 0) { + _edit.mode = p_mode; + _compute_edit(_edit.mouse_pos); + _edit.instant = instant; + _edit.snap = spatial_editor->is_snap_enabled(); + } +} + +void Node3DEditorViewport::commit_transform() { + ERR_FAIL_COND(_edit.mode == TRANSFORM_NONE); + static const char *_transform_name[4] = { + TTRC("None"), + TTRC("Rotate"), + // TRANSLATORS: This refers to the movement that changes the position of an object. + TTRC("Translate"), + TTRC("Scale"), + }; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(_transform_name[_edit.mode]); + + List<Node *> &selection = editor_selection->get_selected_node_list(); + + for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { + Node3D *sp = Object::cast_to<Node3D>(E->get()); + if (!sp) { + continue; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } + + undo_redo->add_do_method(sp, "set_global_transform", sp->get_global_gizmo_transform()); + undo_redo->add_undo_method(sp, "set_global_transform", se->original); + } + undo_redo->commit_action(); + + finish_transform(); + set_message(""); +} + +void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) { + Vector3 ray_pos = _get_ray_pos(p_mousepos); + Vector3 ray = _get_ray(p_mousepos); + double snap = EDITOR_GET("interface/inspector/default_float_step"); + int snap_step_decimals = Math::range_step_decimals(snap); + + switch (_edit.mode) { + case TRANSFORM_SCALE: { + Vector3 motion_mask; + Plane plane; + bool plane_mv = false; + + switch (_edit.plane) { + case TRANSFORM_VIEW: + motion_mask = Vector3(0, 0, 0); + plane = Plane(_get_camera_normal(), _edit.center); + break; + case TRANSFORM_X_AXIS: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); + break; + case TRANSFORM_Y_AXIS: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); + break; + case TRANSFORM_Z_AXIS: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); + break; + case TRANSFORM_YZ: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(), _edit.center); + plane_mv = true; + break; + case TRANSFORM_XZ: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(), _edit.center); + plane_mv = true; + break; + case TRANSFORM_XY: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(), _edit.center); + plane_mv = true; + break; + } + + Vector3 intersection; + if (!plane.intersects_ray(ray_pos, ray, &intersection)) { + break; + } + + Vector3 click; + if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) { + break; + } + + Vector3 motion = intersection - click; + if (_edit.plane != TRANSFORM_VIEW) { + if (!plane_mv) { + motion = motion_mask.dot(motion) * motion_mask; + + } else { + // Alternative planar scaling mode + if (p_shift) { + motion = motion_mask.dot(motion) * motion_mask; + } + } + + } else { + const real_t center_click_dist = click.distance_to(_edit.center); + const real_t center_inters_dist = intersection.distance_to(_edit.center); + if (center_click_dist == 0) { + break; + } + + const real_t scale = center_inters_dist - center_click_dist; + motion = Vector3(scale, scale, scale); + } + + motion /= click.distance_to(_edit.center); + + // Disable local transformation for TRANSFORM_VIEW + bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); + + if (_edit.snap || spatial_editor->is_snap_enabled()) { + snap = spatial_editor->get_scale_snap() / 100; + } + Vector3 motion_snapped = motion; + motion_snapped.snap(Vector3(snap, snap, snap)); + // This might not be necessary anymore after issue #288 is solved (in 4.0?). + // TRANSLATORS: Refers to changing the scale of a node in the 3D editor. + set_message(TTR("Scaling:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " + + String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")"); + if (local_coords) { + motion = _edit.original.basis.inverse().xform(motion); + } + + List<Node *> &selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E); + if (!sp) { + continue; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } + + if (sp->has_meta("_edit_lock_")) { + continue; + } + + if (se->gizmo.is_valid()) { + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; + Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords, _edit.plane != TRANSFORM_VIEW); // Force orthogonal with subgizmo. + if (!local_coords) { + new_xform = se->original.affine_inverse() * new_xform; + } + se->gizmo->set_subgizmo_transform(GE.key, new_xform); + } + } else { + Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS && _edit.plane != TRANSFORM_VIEW); + _transform_gizmo_apply(se->sp, new_xform, local_coords); + } + } + + spatial_editor->update_transform_gizmo(); + surface->queue_redraw(); + + } break; + + case TRANSFORM_TRANSLATE: { + Vector3 motion_mask; + Plane plane; + bool plane_mv = false; + + switch (_edit.plane) { + case TRANSFORM_VIEW: + plane = Plane(_get_camera_normal(), _edit.center); + break; + case TRANSFORM_X_AXIS: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); + break; + case TRANSFORM_Y_AXIS: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); + break; + case TRANSFORM_Z_AXIS: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); + break; + case TRANSFORM_YZ: + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(), _edit.center); + plane_mv = true; + break; + case TRANSFORM_XZ: + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(), _edit.center); + plane_mv = true; + break; + case TRANSFORM_XY: + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(), _edit.center); + plane_mv = true; + break; + } + + Vector3 intersection; + if (!plane.intersects_ray(ray_pos, ray, &intersection)) { + break; + } + + Vector3 click; + if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) { + break; + } + + Vector3 motion = intersection - click; + if (_edit.plane != TRANSFORM_VIEW) { + if (!plane_mv) { + motion = motion_mask.dot(motion) * motion_mask; + } + } + + // Disable local transformation for TRANSFORM_VIEW + bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); + + if (_edit.snap || spatial_editor->is_snap_enabled()) { + snap = spatial_editor->get_translate_snap(); + } + Vector3 motion_snapped = motion; + motion_snapped.snap(Vector3(snap, snap, snap)); + // TRANSLATORS: Refers to changing the position of a node in the 3D editor. + set_message(TTR("Translating:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " + + String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")"); + if (local_coords) { + motion = spatial_editor->get_gizmo_transform().basis.inverse().xform(motion); + } + + List<Node *> &selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E); + if (!sp) { + continue; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } + + if (sp->has_meta("_edit_lock_")) { + continue; + } + + if (se->gizmo.is_valid()) { + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; + Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords, true); // Force orthogonal with subgizmo. + new_xform = se->original.affine_inverse() * new_xform; + se->gizmo->set_subgizmo_transform(GE.key, new_xform); + } + } else { + Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS); + _transform_gizmo_apply(se->sp, new_xform, false); + } + } + + spatial_editor->update_transform_gizmo(); + surface->queue_redraw(); + + } break; + + case TRANSFORM_ROTATE: { + Plane plane = Plane(_get_camera_normal(), _edit.center); + + Vector3 local_axis; + Vector3 global_axis; + switch (_edit.plane) { + case TRANSFORM_VIEW: + // local_axis unused + global_axis = _get_camera_normal(); + break; + case TRANSFORM_X_AXIS: + local_axis = Vector3(1, 0, 0); + break; + case TRANSFORM_Y_AXIS: + local_axis = Vector3(0, 1, 0); + break; + case TRANSFORM_Z_AXIS: + local_axis = Vector3(0, 0, 1); + break; + case TRANSFORM_YZ: + case TRANSFORM_XZ: + case TRANSFORM_XY: + break; + } + + if (_edit.plane != TRANSFORM_VIEW) { + global_axis = spatial_editor->get_gizmo_transform().basis.xform(local_axis).normalized(); + } + + Vector3 intersection; + if (!plane.intersects_ray(ray_pos, ray, &intersection)) { + break; + } + + Vector3 click; + if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) { + break; + } + + static const float orthogonal_threshold = Math::cos(Math::deg_to_rad(87.0f)); + bool axis_is_orthogonal = ABS(plane.normal.dot(global_axis)) < orthogonal_threshold; + + double angle = 0.0f; + if (axis_is_orthogonal) { + _edit.show_rotation_line = false; + Vector3 projection_axis = plane.normal.cross(global_axis); + Vector3 delta = intersection - click; + float projection = delta.dot(projection_axis); + angle = (projection * (Math_PI / 2.0f)) / (gizmo_scale * GIZMO_CIRCLE_SIZE); + } else { + _edit.show_rotation_line = true; + Vector3 click_axis = (click - _edit.center).normalized(); + Vector3 current_axis = (intersection - _edit.center).normalized(); + angle = click_axis.signed_angle_to(current_axis, global_axis); + } + + if (_edit.snap || spatial_editor->is_snap_enabled()) { + snap = spatial_editor->get_rotate_snap(); + } + angle = Math::rad_to_deg(angle) + snap * 0.5; //else it won't reach +180 + angle -= Math::fmod(angle, snap); + set_message(vformat(TTR("Rotating %s degrees."), String::num(angle, snap_step_decimals))); + angle = Math::deg_to_rad(angle); + + bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW + + List<Node *> &selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E); + if (!sp) { + continue; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } + + if (sp->has_meta("_edit_lock_")) { + continue; + } + + Vector3 compute_axis = local_coords ? local_axis : global_axis; + if (se->gizmo.is_valid()) { + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; + + Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords, true); // Force orthogonal with subgizmo. + if (!local_coords) { + new_xform = se->original.affine_inverse() * new_xform; + } + se->gizmo->set_subgizmo_transform(GE.key, new_xform); + } + } else { + Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original, se->original_local, compute_axis, angle, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS); + _transform_gizmo_apply(se->sp, new_xform, local_coords); + } + } + + spatial_editor->update_transform_gizmo(); + surface->queue_redraw(); + + } break; + default: { + } + } +} + +void Node3DEditorViewport::finish_transform() { + spatial_editor->set_local_coords_enabled(_edit.original_local); + spatial_editor->update_transform_gizmo(); + _edit.mode = TRANSFORM_NONE; + _edit.instant = false; + surface->queue_redraw(); +} + +// Register a shortcut and also add it as an input action with the same events. +void Node3DEditorViewport::register_shortcut_action(const String &p_path, const String &p_name, Key p_keycode) { + Ref<Shortcut> sc = ED_SHORTCUT(p_path, p_name, p_keycode); + shortcut_changed_callback(sc, p_path); + // Connect to the change event on the shortcut so the input binding can be updated. + sc->connect("changed", callable_mp(this, &Node3DEditorViewport::shortcut_changed_callback).bind(sc, p_path)); +} + +// Update the action in the InputMap to the provided shortcut events. +void Node3DEditorViewport::shortcut_changed_callback(const Ref<Shortcut> p_shortcut, const String &p_shortcut_path) { + InputMap *im = InputMap::get_singleton(); + if (im->has_action(p_shortcut_path)) { + im->action_erase_events(p_shortcut_path); + } else { + im->add_action(p_shortcut_path); + } + + for (int i = 0; i < p_shortcut->get_events().size(); i++) { + im->action_add_event(p_shortcut_path, p_shortcut->get_events()[i]); + } +} + +Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p_index) { cpu_time_history_index = 0; gpu_time_history_index = 0; _edit.mode = TRANSFORM_NONE; _edit.plane = TRANSFORM_VIEW; _edit.snap = true; + _edit.show_rotation_line = true; + _edit.instant = false; _edit.gizmo_handle = -1; + _edit.gizmo_handle_secondary = false; index = p_index; - editor = p_editor; - editor_data = editor->get_scene_tree_dock()->get_editor_data(); - editor_selection = editor->get_editor_selection(); - undo_redo = editor->get_undo_redo(); + editor_selection = EditorNode::get_singleton()->get_editor_selection(); orthogonal = false; auto_orthogonal = false; @@ -4316,15 +4908,15 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito subviewport_container = c; c->set_stretch(true); add_child(c); - c->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + c->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); viewport = memnew(SubViewport); viewport->set_disable_input(true); c->add_child(viewport); surface = memnew(Control); - surface->set_drag_forwarding(this); + SET_DRAG_FORWARDING_CD(surface, Node3DEditorViewport); add_child(surface); - surface->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + surface->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); surface->set_clip_contents(true); camera = memnew(Camera3D); camera->set_disable_gizmos(true); @@ -4345,6 +4937,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito vbox->add_child(view_menu); display_submenu = memnew(PopupMenu); + view_menu->get_popup()->set_hide_on_checkable_item_selection(false); view_menu->get_popup()->add_child(display_submenu); view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/top_view"), VIEW_TOP); @@ -4354,8 +4947,9 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/front_view"), VIEW_FRONT); view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/rear_view"), VIEW_REAR); view_menu->get_popup()->add_separator(); - view_menu->get_popup()->add_radio_check_item(TTR("Perspective") + " (" + ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal")->get_as_text() + ")", VIEW_PERSPECTIVE); - view_menu->get_popup()->add_radio_check_item(TTR("Orthogonal") + " (" + ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal")->get_as_text() + ")", VIEW_ORTHOGONAL); + view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal"), VIEW_SWITCH_PERSPECTIVE_ORTHOGONAL); + view_menu->get_popup()->add_radio_check_item(TTR("Perspective"), VIEW_PERSPECTIVE); + view_menu->get_popup()->add_radio_check_item(TTR("Orthogonal"), VIEW_ORTHOGONAL); view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), true); view_menu->get_popup()->add_check_item(TTR("Auto Orthogonal Enabled"), VIEW_AUTO_ORTHOGONAL); view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL), true); @@ -4368,12 +4962,13 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_lighting", TTR("Display Lighting")), VIEW_DISPLAY_LIGHTING); view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTR("Display Unshaded")), VIEW_DISPLAY_SHADELESS); view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), true); + display_submenu->set_hide_on_checkable_item_selection(false); display_submenu->add_radio_check_item(TTR("Directional Shadow Splits"), VIEW_DISPLAY_DEBUG_PSSM_SPLITS); display_submenu->add_separator(); display_submenu->add_radio_check_item(TTR("Normal Buffer"), VIEW_DISPLAY_NORMAL_BUFFER); display_submenu->add_separator(); display_submenu->add_radio_check_item(TTR("Shadow Atlas"), VIEW_DISPLAY_DEBUG_SHADOW_ATLAS); - display_submenu->add_radio_check_item(TTR("Directional Shadow"), VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS); + display_submenu->add_radio_check_item(TTR("Directional Shadow Map"), VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS); display_submenu->add_separator(); display_submenu->add_radio_check_item(TTR("Decal Atlas"), VIEW_DISPLAY_DEBUG_DECAL_ATLAS); display_submenu->add_separator(); @@ -4389,15 +4984,16 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito display_submenu->add_radio_check_item(TTR("SSAO"), VIEW_DISPLAY_DEBUG_SSAO); display_submenu->add_radio_check_item(TTR("SSIL"), VIEW_DISPLAY_DEBUG_SSIL); display_submenu->add_separator(); - display_submenu->add_radio_check_item(TTR("GI Buffer"), VIEW_DISPLAY_DEBUG_GI_BUFFER); + display_submenu->add_radio_check_item(TTR("VoxelGI/SDFGI Buffer"), VIEW_DISPLAY_DEBUG_GI_BUFFER); display_submenu->add_separator(); - display_submenu->add_radio_check_item(TTR("Disable LOD"), VIEW_DISPLAY_DEBUG_DISABLE_LOD); + display_submenu->add_radio_check_item(TTR("Disable Mesh LOD"), VIEW_DISPLAY_DEBUG_DISABLE_LOD); display_submenu->add_separator(); - display_submenu->add_radio_check_item(TTR("Omni Light Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS); - display_submenu->add_radio_check_item(TTR("Spot Light Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS); + display_submenu->add_radio_check_item(TTR("OmniLight3D Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS); + display_submenu->add_radio_check_item(TTR("SpotLight3D Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS); display_submenu->add_radio_check_item(TTR("Decal Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_DECALS); - display_submenu->add_radio_check_item(TTR("Reflection Probe Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES); + display_submenu->add_radio_check_item(TTR("ReflectionProbe Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES); display_submenu->add_radio_check_item(TTR("Occlusion Culling Buffer"), VIEW_DISPLAY_DEBUG_OCCLUDERS); + display_submenu->add_radio_check_item(TTR("Motion Vectors"), VIEW_DISPLAY_MOTION_VECTORS); display_submenu->set_name("display_advanced"); view_menu->get_popup()->add_submenu_item(TTR("Display Advanced..."), "display_advanced", VIEW_DISPLAY_ADVANCED); @@ -4425,10 +5021,9 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito view_menu->get_popup()->connect("id_pressed", callable_mp(this, &Node3DEditorViewport::_menu_option)); display_submenu->connect("id_pressed", callable_mp(this, &Node3DEditorViewport::_menu_option)); view_menu->set_disable_shortcuts(true); -#ifndef _MSC_VER -#warning this needs to be fixed -#endif - //if (OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2) { + + // TODO: Re-evaluate with new OpenGL3 renderer, and implement. + //if (OS::get_singleton()->get_current_video_driver() == OS::RENDERING_DRIVER_OPENGL3) { if (false) { // Alternate display modes only work when using the Vulkan renderer; make this explicit. const int normal_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL); @@ -4447,18 +5042,29 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito view_menu->get_popup()->set_item_tooltip(shadeless_idx, unsupported_tooltip); } - ED_SHORTCUT("spatial_editor/freelook_left", TTR("Freelook Left"), Key::A); - ED_SHORTCUT("spatial_editor/freelook_right", TTR("Freelook Right"), Key::D); - ED_SHORTCUT("spatial_editor/freelook_forward", TTR("Freelook Forward"), Key::W); - ED_SHORTCUT("spatial_editor/freelook_backwards", TTR("Freelook Backwards"), Key::S); - ED_SHORTCUT("spatial_editor/freelook_up", TTR("Freelook Up"), Key::E); - ED_SHORTCUT("spatial_editor/freelook_down", TTR("Freelook Down"), Key::Q); - ED_SHORTCUT("spatial_editor/freelook_speed_modifier", TTR("Freelook Speed Modifier"), Key::SHIFT); - ED_SHORTCUT("spatial_editor/freelook_slow_modifier", TTR("Freelook Slow Modifier"), Key::ALT); + register_shortcut_action("spatial_editor/freelook_left", TTR("Freelook Left"), Key::A); + register_shortcut_action("spatial_editor/freelook_right", TTR("Freelook Right"), Key::D); + register_shortcut_action("spatial_editor/freelook_forward", TTR("Freelook Forward"), Key::W); + register_shortcut_action("spatial_editor/freelook_backwards", TTR("Freelook Backwards"), Key::S); + register_shortcut_action("spatial_editor/freelook_up", TTR("Freelook Up"), Key::E); + register_shortcut_action("spatial_editor/freelook_down", TTR("Freelook Down"), Key::Q); + register_shortcut_action("spatial_editor/freelook_speed_modifier", TTR("Freelook Speed Modifier"), Key::SHIFT); + register_shortcut_action("spatial_editor/freelook_slow_modifier", TTR("Freelook Slow Modifier"), Key::ALT); + + ED_SHORTCUT("spatial_editor/lock_transform_x", TTR("Lock Transformation to X axis"), Key::X); + ED_SHORTCUT("spatial_editor/lock_transform_y", TTR("Lock Transformation to Y axis"), Key::Y); + ED_SHORTCUT("spatial_editor/lock_transform_z", TTR("Lock Transformation to Z axis"), Key::Z); + ED_SHORTCUT("spatial_editor/lock_transform_yz", TTR("Lock Transformation to YZ plane"), KeyModifierMask::SHIFT | Key::X); + ED_SHORTCUT("spatial_editor/lock_transform_xz", TTR("Lock Transformation to XZ plane"), KeyModifierMask::SHIFT | Key::Y); + ED_SHORTCUT("spatial_editor/lock_transform_xy", TTR("Lock Transformation to XY plane"), KeyModifierMask::SHIFT | Key::Z); + ED_SHORTCUT("spatial_editor/cancel_transform", TTR("Cancel Transformation"), Key::ESCAPE); + ED_SHORTCUT("spatial_editor/instant_translate", TTR("Begin Translate Transformation")); + ED_SHORTCUT("spatial_editor/instant_rotate", TTR("Begin Rotate Transformation")); + ED_SHORTCUT("spatial_editor/instant_scale", TTR("Begin Scale Transformation")); preview_camera = memnew(CheckBox); preview_camera->set_text(TTR("Preview")); - preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTR("Toggle Camera Preview"), KeyModifierMask::CMD | Key::P)); + preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTR("Toggle Camera Preview"), KeyModifierMask::CMD_OR_CTRL | Key::P)); vbox->add_child(preview_camera); preview_camera->set_h_size_flags(0); preview_camera->hide(); @@ -4468,6 +5074,14 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito preview_node = nullptr; + bottom_center_vbox = memnew(VBoxContainer); + bottom_center_vbox->set_anchors_preset(LayoutPreset::PRESET_CENTER); + bottom_center_vbox->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -20 * EDSCALE); + bottom_center_vbox->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE); + bottom_center_vbox->set_h_grow_direction(GROW_DIRECTION_BOTH); + bottom_center_vbox->set_v_grow_direction(GROW_DIRECTION_BEGIN); + surface->add_child(bottom_center_vbox); + info_label = memnew(Label); info_label->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -90 * EDSCALE); info_label->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -90 * EDSCALE); @@ -4488,36 +5102,72 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito previewing_cinema = false; locked_label = memnew(Label); - locked_label->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -20 * EDSCALE); - locked_label->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE); - locked_label->set_h_grow_direction(GROW_DIRECTION_END); - locked_label->set_v_grow_direction(GROW_DIRECTION_BEGIN); locked_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); - surface->add_child(locked_label); + locked_label->set_h_size_flags(SIZE_SHRINK_CENTER); + bottom_center_vbox->add_child(locked_label); locked_label->set_text(TTR("View Rotation Locked")); locked_label->hide(); zoom_limit_label = memnew(Label); - zoom_limit_label->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT); - zoom_limit_label->set_offset(Side::SIDE_TOP, -28 * EDSCALE); zoom_limit_label->set_text(TTR("To zoom further, change the camera's clipping planes (View -> Settings...)")); zoom_limit_label->set_name("ZoomLimitMessageLabel"); zoom_limit_label->add_theme_color_override("font_color", Color(1, 1, 1, 1)); zoom_limit_label->hide(); - surface->add_child(zoom_limit_label); + bottom_center_vbox->add_child(zoom_limit_label); + + preview_material_label = memnew(Label); + preview_material_label->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT); + preview_material_label->set_offset(Side::SIDE_TOP, -70 * EDSCALE); + preview_material_label->set_text(TTR("Overriding material...")); + preview_material_label->add_theme_color_override("font_color", Color(1, 1, 1, 1)); + preview_material_label->hide(); + surface->add_child(preview_material_label); + + preview_material_label_desc = memnew(Label); + preview_material_label_desc->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT); + preview_material_label_desc->set_offset(Side::SIDE_TOP, -50 * EDSCALE); + preview_material_label_desc->set_text(TTR("Drag and drop to override the material of any geometry node.\nHold Ctrl when dropping to override a specific surface.")); + preview_material_label_desc->add_theme_color_override("font_color", Color(0.8, 0.8, 0.8, 1)); + preview_material_label_desc->add_theme_constant_override("line_spacing", 0); + preview_material_label_desc->hide(); + surface->add_child(preview_material_label_desc); frame_time_gradient = memnew(Gradient); // The color is set when the theme changes. frame_time_gradient->add_point(0.5, Color()); top_right_vbox = memnew(VBoxContainer); - top_right_vbox->set_anchors_and_offsets_preset(PRESET_TOP_RIGHT, PRESET_MODE_MINSIZE, 2.0 * EDSCALE); + top_right_vbox->set_anchors_and_offsets_preset(PRESET_TOP_RIGHT, PRESET_MODE_MINSIZE, 10.0 * EDSCALE); top_right_vbox->set_h_grow_direction(GROW_DIRECTION_BEGIN); // Make sure frame time labels don't touch the viewport's edge. top_right_vbox->set_custom_minimum_size(Size2(100, 0) * EDSCALE); // Prevent visible spacing between frame time labels. top_right_vbox->add_theme_constant_override("separation", 0); + const int navigation_control_size = 150; + + position_control = memnew(ViewportNavigationControl); + position_control->set_navigation_mode(Node3DEditorViewport::NAVIGATION_MOVE); + position_control->set_custom_minimum_size(Size2(navigation_control_size, navigation_control_size) * EDSCALE); + position_control->set_h_size_flags(SIZE_SHRINK_END); + position_control->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, 0 * EDSCALE); + position_control->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -navigation_control_size * EDSCALE); + position_control->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_BEGIN, navigation_control_size * EDSCALE); + position_control->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0 * EDSCALE); + position_control->set_viewport(this); + surface->add_child(position_control); + + look_control = memnew(ViewportNavigationControl); + look_control->set_navigation_mode(Node3DEditorViewport::NAVIGATION_LOOK); + look_control->set_custom_minimum_size(Size2(navigation_control_size, navigation_control_size) * EDSCALE); + look_control->set_h_size_flags(SIZE_SHRINK_END); + look_control->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -navigation_control_size * EDSCALE); + look_control->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -navigation_control_size * EDSCALE); + look_control->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0 * EDSCALE); + look_control->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0 * EDSCALE); + look_control->set_viewport(this); + surface->add_child(look_control); + rotation_control = memnew(ViewportRotationControl); rotation_control->set_custom_minimum_size(Size2(80, 80) * EDSCALE); rotation_control->set_h_size_flags(SIZE_SHRINK_END); @@ -4542,7 +5192,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito accept = nullptr; freelook_active = false; - freelook_speed = EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_base_speed"); + freelook_speed = EDITOR_GET("editors/3d/freelook/freelook_base_speed"); selection_menu = memnew(PopupMenu); add_child(selection_menu); @@ -4634,7 +5284,7 @@ void Node3DEditorViewportContainer::gui_input(const Ref<InputEvent> &p_event) { hovering_v = mm->get_position().y > (mid_h - v_sep / 2) && mm->get_position().y < (mid_h + v_sep / 2); if (was_hovering_h != hovering_h || was_hovering_v != hovering_v) { - update(); + queue_redraw(); } } @@ -4643,210 +5293,215 @@ void Node3DEditorViewportContainer::gui_input(const Ref<InputEvent> &p_event) { new_ratio = CLAMP(new_ratio, 40 / get_size().width, (get_size().width - 40) / get_size().width); ratio_h = new_ratio; queue_sort(); - update(); + queue_redraw(); } if (dragging_v) { real_t new_ratio = drag_begin_ratio.y + (mm->get_position().y - drag_begin_pos.y) / get_size().height; new_ratio = CLAMP(new_ratio, 40 / get_size().height, (get_size().height - 40) / get_size().height); ratio_v = new_ratio; queue_sort(); - update(); + queue_redraw(); } } } void Node3DEditorViewportContainer::_notification(int p_what) { - if (p_what == NOTIFICATION_MOUSE_ENTER || p_what == NOTIFICATION_MOUSE_EXIT) { - mouseover = (p_what == NOTIFICATION_MOUSE_ENTER); - update(); - } + switch (p_what) { + case NOTIFICATION_MOUSE_ENTER: + case NOTIFICATION_MOUSE_EXIT: { + mouseover = (p_what == NOTIFICATION_MOUSE_ENTER); + queue_redraw(); + } break; - if (p_what == NOTIFICATION_DRAW && mouseover) { - Ref<Texture2D> h_grabber = get_theme_icon(SNAME("grabber"), SNAME("HSplitContainer")); - Ref<Texture2D> v_grabber = get_theme_icon(SNAME("grabber"), SNAME("VSplitContainer")); + case NOTIFICATION_DRAW: { + if (mouseover) { + Ref<Texture2D> h_grabber = get_theme_icon(SNAME("grabber"), SNAME("HSplitContainer")); + Ref<Texture2D> v_grabber = get_theme_icon(SNAME("grabber"), SNAME("VSplitContainer")); - Ref<Texture2D> hdiag_grabber = get_theme_icon(SNAME("GuiViewportHdiagsplitter"), SNAME("EditorIcons")); - Ref<Texture2D> vdiag_grabber = get_theme_icon(SNAME("GuiViewportVdiagsplitter"), SNAME("EditorIcons")); - Ref<Texture2D> vh_grabber = get_theme_icon(SNAME("GuiViewportVhsplitter"), SNAME("EditorIcons")); + Ref<Texture2D> hdiag_grabber = get_theme_icon(SNAME("GuiViewportHdiagsplitter"), SNAME("EditorIcons")); + Ref<Texture2D> vdiag_grabber = get_theme_icon(SNAME("GuiViewportVdiagsplitter"), SNAME("EditorIcons")); + Ref<Texture2D> vh_grabber = get_theme_icon(SNAME("GuiViewportVhsplitter"), SNAME("EditorIcons")); - Vector2 size = get_size(); + Vector2 size = get_size(); - int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer")); + int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer")); - int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer")); + int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer")); - int mid_w = size.width * ratio_h; - int mid_h = size.height * ratio_v; + int mid_w = size.width * ratio_h; + int mid_h = size.height * ratio_v; - int size_left = mid_w - h_sep / 2; - int size_bottom = size.height - mid_h - v_sep / 2; + int size_left = mid_w - h_sep / 2; + int size_bottom = size.height - mid_h - v_sep / 2; - switch (view) { - case VIEW_USE_1_VIEWPORT: { - // Nothing to show. + switch (view) { + case VIEW_USE_1_VIEWPORT: { + // Nothing to show. - } break; - case VIEW_USE_2_VIEWPORTS: { - draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2)); - set_default_cursor_shape(CURSOR_VSPLIT); + } break; + case VIEW_USE_2_VIEWPORTS: { + draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2)); + set_default_cursor_shape(CURSOR_VSPLIT); - } break; - case VIEW_USE_2_VIEWPORTS_ALT: { - draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2)); - set_default_cursor_shape(CURSOR_HSPLIT); + } break; + case VIEW_USE_2_VIEWPORTS_ALT: { + draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2)); + set_default_cursor_shape(CURSOR_HSPLIT); - } break; - case VIEW_USE_3_VIEWPORTS: { - if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) { - draw_texture(hdiag_grabber, Vector2(mid_w - hdiag_grabber->get_width() / 2, mid_h - v_grabber->get_height() / 4)); - set_default_cursor_shape(CURSOR_DRAG); - } else if ((hovering_v && !dragging_h) || dragging_v) { - draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2)); - set_default_cursor_shape(CURSOR_VSPLIT); - } else if (hovering_h || dragging_h) { - draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, mid_h + v_grabber->get_height() / 2 + (size_bottom - h_grabber->get_height()) / 2)); - set_default_cursor_shape(CURSOR_HSPLIT); - } + } break; + case VIEW_USE_3_VIEWPORTS: { + if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) { + draw_texture(hdiag_grabber, Vector2(mid_w - hdiag_grabber->get_width() / 2, mid_h - v_grabber->get_height() / 4)); + set_default_cursor_shape(CURSOR_DRAG); + } else if ((hovering_v && !dragging_h) || dragging_v) { + draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2)); + set_default_cursor_shape(CURSOR_VSPLIT); + } else if (hovering_h || dragging_h) { + draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, mid_h + v_grabber->get_height() / 2 + (size_bottom - h_grabber->get_height()) / 2)); + set_default_cursor_shape(CURSOR_HSPLIT); + } - } break; - case VIEW_USE_3_VIEWPORTS_ALT: { - if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) { - draw_texture(vdiag_grabber, Vector2(mid_w - vdiag_grabber->get_width() + v_grabber->get_height() / 4, mid_h - vdiag_grabber->get_height() / 2)); - set_default_cursor_shape(CURSOR_DRAG); - } else if ((hovering_v && !dragging_h) || dragging_v) { - draw_texture(v_grabber, Vector2((size_left - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2)); - set_default_cursor_shape(CURSOR_VSPLIT); - } else if (hovering_h || dragging_h) { - draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2)); - set_default_cursor_shape(CURSOR_HSPLIT); - } + } break; + case VIEW_USE_3_VIEWPORTS_ALT: { + if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) { + draw_texture(vdiag_grabber, Vector2(mid_w - vdiag_grabber->get_width() + v_grabber->get_height() / 4, mid_h - vdiag_grabber->get_height() / 2)); + set_default_cursor_shape(CURSOR_DRAG); + } else if ((hovering_v && !dragging_h) || dragging_v) { + draw_texture(v_grabber, Vector2((size_left - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2)); + set_default_cursor_shape(CURSOR_VSPLIT); + } else if (hovering_h || dragging_h) { + draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2)); + set_default_cursor_shape(CURSOR_HSPLIT); + } - } break; - case VIEW_USE_4_VIEWPORTS: { - Vector2 half(mid_w, mid_h); - if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) { - draw_texture(vh_grabber, half - vh_grabber->get_size() / 2.0); - set_default_cursor_shape(CURSOR_DRAG); - } else if ((hovering_v && !dragging_h) || dragging_v) { - draw_texture(v_grabber, half - v_grabber->get_size() / 2.0); - set_default_cursor_shape(CURSOR_VSPLIT); - } else if (hovering_h || dragging_h) { - draw_texture(h_grabber, half - h_grabber->get_size() / 2.0); - set_default_cursor_shape(CURSOR_HSPLIT); - } + } break; + case VIEW_USE_4_VIEWPORTS: { + Vector2 half(mid_w, mid_h); + if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) { + draw_texture(vh_grabber, half - vh_grabber->get_size() / 2.0); + set_default_cursor_shape(CURSOR_DRAG); + } else if ((hovering_v && !dragging_h) || dragging_v) { + draw_texture(v_grabber, half - v_grabber->get_size() / 2.0); + set_default_cursor_shape(CURSOR_VSPLIT); + } else if (hovering_h || dragging_h) { + draw_texture(h_grabber, half - h_grabber->get_size() / 2.0); + set_default_cursor_shape(CURSOR_HSPLIT); + } - } break; - } - } + } break; + } + } + } break; - if (p_what == NOTIFICATION_SORT_CHILDREN) { - Node3DEditorViewport *viewports[4]; - int vc = 0; - for (int i = 0; i < get_child_count(); i++) { - viewports[vc] = Object::cast_to<Node3DEditorViewport>(get_child(i)); - if (viewports[vc]) { - vc++; + case NOTIFICATION_SORT_CHILDREN: { + Node3DEditorViewport *viewports[4]; + int vc = 0; + for (int i = 0; i < get_child_count(); i++) { + viewports[vc] = Object::cast_to<Node3DEditorViewport>(get_child(i)); + if (viewports[vc]) { + vc++; + } } - } - ERR_FAIL_COND(vc != 4); + ERR_FAIL_COND(vc != 4); - Size2 size = get_size(); + Size2 size = get_size(); - if (size.x < 10 || size.y < 10) { - for (int i = 0; i < 4; i++) { - viewports[i]->hide(); + if (size.x < 10 || size.y < 10) { + for (int i = 0; i < 4; i++) { + viewports[i]->hide(); + } + return; } - return; - } - int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer")); + int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer")); - int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer")); + int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer")); - int mid_w = size.width * ratio_h; - int mid_h = size.height * ratio_v; + int mid_w = size.width * ratio_h; + int mid_h = size.height * ratio_v; - int size_left = mid_w - h_sep / 2; - int size_right = size.width - mid_w - h_sep / 2; + int size_left = mid_w - h_sep / 2; + int size_right = size.width - mid_w - h_sep / 2; - int size_top = mid_h - v_sep / 2; - int size_bottom = size.height - mid_h - v_sep / 2; + int size_top = mid_h - v_sep / 2; + int size_bottom = size.height - mid_h - v_sep / 2; - switch (view) { - case VIEW_USE_1_VIEWPORT: { - viewports[0]->show(); - for (int i = 1; i < 4; i++) { - viewports[i]->hide(); - } + switch (view) { + case VIEW_USE_1_VIEWPORT: { + viewports[0]->show(); + for (int i = 1; i < 4; i++) { + viewports[i]->hide(); + } - fit_child_in_rect(viewports[0], Rect2(Vector2(), size)); + fit_child_in_rect(viewports[0], Rect2(Vector2(), size)); - } break; - case VIEW_USE_2_VIEWPORTS: { - for (int i = 0; i < 4; i++) { - if (i == 1 || i == 3) { - viewports[i]->hide(); - } else { - viewports[i]->show(); + } break; + case VIEW_USE_2_VIEWPORTS: { + for (int i = 0; i < 4; i++) { + if (i == 1 || i == 3) { + viewports[i]->hide(); + } else { + viewports[i]->show(); + } } - } - fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top))); - fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size.width, size_bottom))); + fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top))); + fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size.width, size_bottom))); - } break; - case VIEW_USE_2_VIEWPORTS_ALT: { - for (int i = 0; i < 4; i++) { - if (i == 1 || i == 3) { - viewports[i]->hide(); - } else { - viewports[i]->show(); + } break; + case VIEW_USE_2_VIEWPORTS_ALT: { + for (int i = 0; i < 4; i++) { + if (i == 1 || i == 3) { + viewports[i]->hide(); + } else { + viewports[i]->show(); + } } - } - fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size.height))); - fit_child_in_rect(viewports[2], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height))); + fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size.height))); + fit_child_in_rect(viewports[2], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height))); - } break; - case VIEW_USE_3_VIEWPORTS: { - for (int i = 0; i < 4; i++) { - if (i == 1) { - viewports[i]->hide(); - } else { - viewports[i]->show(); + } break; + case VIEW_USE_3_VIEWPORTS: { + for (int i = 0; i < 4; i++) { + if (i == 1) { + viewports[i]->hide(); + } else { + viewports[i]->show(); + } } - } - fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top))); - fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom))); - fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom))); + fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top))); + fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom))); + fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom))); - } break; - case VIEW_USE_3_VIEWPORTS_ALT: { - for (int i = 0; i < 4; i++) { - if (i == 1) { - viewports[i]->hide(); - } else { - viewports[i]->show(); + } break; + case VIEW_USE_3_VIEWPORTS_ALT: { + for (int i = 0; i < 4; i++) { + if (i == 1) { + viewports[i]->hide(); + } else { + viewports[i]->show(); + } } - } - fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top))); - fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom))); - fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height))); + fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top))); + fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom))); + fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height))); - } break; - case VIEW_USE_4_VIEWPORTS: { - for (int i = 0; i < 4; i++) { - viewports[i]->show(); - } + } break; + case VIEW_USE_4_VIEWPORTS: { + for (int i = 0; i < 4; i++) { + viewports[i]->show(); + } - fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top))); - fit_child_in_rect(viewports[1], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size_top))); - fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom))); - fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom))); + fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top))); + fit_child_in_rect(viewports[1], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size_top))); + fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom))); + fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom))); - } break; - } + } break; + } + } break; } } @@ -4876,6 +5531,7 @@ Node3DEditorViewportContainer::Node3DEditorViewportContainer() { Node3DEditor *Node3DEditor::singleton = nullptr; Node3DEditorSelectedItem::~Node3DEditorSelectedItem() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); if (sbox_instance.is_valid()) { RenderingServer::get_singleton()->free(sbox_instance); } @@ -4915,7 +5571,6 @@ void Node3DEditor::update_transform_gizmo() { gizmo_center += xf.origin; if (count == 0 && local_gizmo_coords) { gizmo_basis = xf.basis; - gizmo_basis.orthonormalize(); } count++; } @@ -4940,7 +5595,6 @@ void Node3DEditor::update_transform_gizmo() { gizmo_center += xf.origin; if (count == 0 && local_gizmo_coords) { gizmo_basis = xf.basis; - gizmo_basis.orthonormalize(); } count++; } @@ -5004,7 +5658,9 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { RS::get_singleton()->instance_set_layer_mask(si->sbox_instance, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); si->sbox_instance_xray = RenderingServer::get_singleton()->instance_create2( selection_box_xray->get_rid(), sp->get_world_3d()->get_scenario()); @@ -5022,7 +5678,9 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); return si; } @@ -5133,8 +5791,8 @@ Dictionary Node3DEditor::get_state() const { pd["sun_color"] = sun_color->get_pick_color(); pd["sun_energy"] = sun_energy->get_value(); - pd["sun_disabled"] = sun_button->is_pressed(); - pd["environ_disabled"] = environ_button->is_pressed(); + pd["sun_enabled"] = sun_button->is_pressed(); + pd["environ_enabled"] = environ_button->is_pressed(); d["preview_sun_env"] = pd; } @@ -5265,8 +5923,8 @@ void Node3DEditor::set_state(const Dictionary &p_state) { sun_color->set_pick_color(pd["sun_color"]); sun_energy->set_value(pd["sun_energy"]); - sun_button->set_pressed(pd["sun_disabled"]); - environ_button->set_pressed(pd["environ_disabled"]); + sun_button->set_pressed(pd["sun_enabled"]); + environ_button->set_pressed(pd["environ_enabled"]); sun_environ_updating = false; @@ -5274,8 +5932,8 @@ void Node3DEditor::set_state(const Dictionary &p_state) { _update_preview_environment(); } else { _load_default_preview_settings(); - sun_button->set_pressed(false); - environ_button->set_pressed(false); + sun_button->set_pressed(true); + environ_button->set_pressed(true); _preview_settings_changed(); _update_preview_environment(); } @@ -5305,6 +5963,7 @@ void Node3DEditor::edit(Node3D *p_spatial) { selected = p_spatial; current_hover_gizmo = Ref<EditorNode3DGizmo>(); current_hover_gizmo_handle = -1; + current_hover_gizmo_handle_secondary = false; if (selected) { Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos(); @@ -5341,7 +6000,7 @@ void Node3DEditor::_xform_dialog_action() { for (int i = 0; i < 3; i++) { translate[i] = xform_translate[i]->get_text().to_float(); - rotate[i] = Math::deg2rad(xform_rotate[i]->get_text().to_float()); + rotate[i] = Math::deg_to_rad(xform_rotate[i]->get_text().to_float()); scale[i] = xform_scale[i]->get_text().to_float(); } @@ -5349,9 +6008,10 @@ void Node3DEditor::_xform_dialog_action() { t.basis.rotate(rotate); t.origin = translate; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("XForm Dialog")); - List<Node *> &selection = editor_selection->get_selected_node_list(); + const List<Node *> &selection = editor_selection->get_selected_node_list(); for (Node *E : selection) { Node3D *sp = Object::cast_to<Node3D>(E); @@ -5434,11 +6094,11 @@ void Node3DEditor::_update_camera_override_button(bool p_game_running) { if (p_game_running) { button->set_disabled(false); - button->set_tooltip(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera.")); + button->set_tooltip_text(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera.")); } else { button->set_disabled(true); button->set_pressed(false); - button->set_tooltip(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature.")); + button->set_tooltip_text(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature.")); } } @@ -5460,6 +6120,7 @@ void Node3DEditor::_update_camera_override_viewport(Object *p_viewport) { } void Node3DEditor::_menu_item_pressed(int p_option) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); switch (p_option) { case MENU_TOOL_SELECT: case MENU_TOOL_MOVE: @@ -5771,9 +6432,9 @@ void fragment() { float angle_fade = abs(dot(dir, NORMAL)); angle_fade = smoothstep(0.05, 0.2, angle_fade); - vec3 world_pos = (CAMERA_MATRIX * vec4(VERTEX, 1.0)).xyz; - vec3 world_normal = (CAMERA_MATRIX * vec4(NORMAL, 0.0)).xyz; - vec3 camera_world_pos = CAMERA_MATRIX[3].xyz; + vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz; + vec3 world_normal = (INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz; + vec3 camera_world_pos = INV_VIEW_MATRIX[3].xyz; vec3 camera_world_pos_on_plane = camera_world_pos * (1.0 - world_normal); float dist_fade = 1.0 - (distance(world_pos, camera_world_pos_on_plane) / grid_size); dist_fade = smoothstep(0.02, 0.3, dist_fade); @@ -5787,9 +6448,9 @@ void fragment() { grid_mat[i]->set_shader(grid_shader); } - grid_enable[0] = EditorSettings::get_singleton()->get("editors/3d/grid_xy_plane"); - grid_enable[1] = EditorSettings::get_singleton()->get("editors/3d/grid_yz_plane"); - grid_enable[2] = EditorSettings::get_singleton()->get("editors/3d/grid_xz_plane"); + grid_enable[0] = EDITOR_GET("editors/3d/grid_xy_plane"); + grid_enable[1] = EDITOR_GET("editors/3d/grid_yz_plane"); + grid_enable[2] = EDITOR_GET("editors/3d/grid_xz_plane"); grid_visible[0] = grid_enable[0]; grid_visible[1] = grid_enable[1]; grid_visible[2] = grid_enable[2]; @@ -5808,6 +6469,7 @@ void fragment() { origin_instance = RenderingServer::get_singleton()->instance_create2(origin, get_tree()->get_root()->get_world_3d()->get_scenario()); RS::get_singleton()->instance_set_layer_mask(origin_instance, 1 << Node3DEditorViewport::GIZMO_GRID_LAYER); RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(origin_instance, RS::SHADOW_CASTING_SETTING_OFF); } @@ -5815,6 +6477,12 @@ void fragment() { { //move gizmo + // Inverted zxy. + Vector3 ivec = Vector3(0, 0, -1); + Vector3 nivec = Vector3(-1, -1, 0); + Vector3 ivec2 = Vector3(-1, 0, 0); + Vector3 ivec3 = Vector3(0, -1, 0); + for (int i = 0; i < 3; i++) { Color col; switch (i) { @@ -5832,13 +6500,14 @@ void fragment() { break; } - col.a = EditorSettings::get_singleton()->get("editors/3d/manipulator_gizmo_opacity"); + col.a = EDITOR_GET("editors/3d/manipulator_gizmo_opacity"); move_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh)); move_plane_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh)); rotate_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh)); scale_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh)); scale_plane_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh)); + axis_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh)); Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D); mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); @@ -5852,16 +6521,6 @@ void fragment() { mat_hl->set_albedo(albedo); gizmo_color_hl[i] = mat_hl; - Vector3 ivec; - ivec[i] = 1; - Vector3 nivec; - nivec[(i + 1) % 3] = 1; - nivec[(i + 2) % 3] = 1; - Vector3 ivec2; - ivec2[(i + 1) % 3] = 1; - Vector3 ivec3; - ivec3[(i + 2) % 3] = 1; - //translate { Ref<SurfaceTool> surftool = memnew(SurfaceTool); @@ -6026,7 +6685,7 @@ void fragment() { Ref<ShaderMaterial> rotate_mat = memnew(ShaderMaterial); rotate_mat->set_render_priority(Material::RENDER_PRIORITY_MAX); rotate_mat->set_shader(rotate_shader); - rotate_mat->set_shader_param("albedo", col); + rotate_mat->set_shader_parameter("albedo", col); rotate_gizmo_color[i] = rotate_mat; Array arrays = surftool->commit_to_arrays(); @@ -6034,7 +6693,7 @@ void fragment() { rotate_gizmo[i]->surface_set_material(0, rotate_mat); Ref<ShaderMaterial> rotate_mat_hl = rotate_mat->duplicate(); - rotate_mat_hl->set_shader_param("albedo", albedo); + rotate_mat_hl->set_shader_parameter("albedo", albedo); rotate_gizmo_color_hl[i] = rotate_mat_hl; if (i == 2) { // Rotation white outline @@ -6075,7 +6734,7 @@ void fragment() { )"); border_mat->set_shader(border_shader); - border_mat->set_shader_param("albedo", Color(0.75, 0.75, 0.75, col.a / 3.0)); + border_mat->set_shader_parameter("albedo", Color(0.75, 0.75, 0.75, col.a / 3.0)); rotate_gizmo[3] = Ref<ArrayMesh>(memnew(ArrayMesh)); rotate_gizmo[3]->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays); @@ -6170,24 +6829,28 @@ void fragment() { plane_mat_hl->set_albedo(col.from_hsv(col.get_h(), 0.25, 1.0, 1)); plane_gizmo_color_hl[i] = plane_mat_hl; // needed, so we can draw planes from both sides } + + // Lines to visualize transforms locked to an axis/plane + { + Ref<SurfaceTool> surftool = memnew(SurfaceTool); + surftool->begin(Mesh::PRIMITIVE_LINE_STRIP); + + Vector3 vec; + vec[i] = 1; + + // line extending through infinity(ish) + surftool->add_vertex(vec * -1048576); + surftool->add_vertex(Vector3()); + surftool->add_vertex(vec * 1048576); + surftool->set_material(mat_hl); + surftool->commit(axis_gizmo[i]); + } } } _generate_selection_boxes(); } -void Node3DEditor::_update_context_menu_stylebox() { - // This must be called when the theme changes to follow the new accent color. - Ref<StyleBoxFlat> context_menu_stylebox = memnew(StyleBoxFlat); - const Color accent_color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("accent_color"), SNAME("Editor")); - context_menu_stylebox->set_bg_color(accent_color * Color(1, 1, 1, 0.1)); - // Add an underline to the StyleBox, but prevent its minimum vertical size from changing. - context_menu_stylebox->set_border_color(accent_color); - context_menu_stylebox->set_border_width(SIDE_BOTTOM, Math::round(2 * EDSCALE)); - context_menu_stylebox->set_default_margin(SIDE_BOTTOM, 0); - context_menu_container->add_theme_style_override("panel", context_menu_stylebox); -} - void Node3DEditor::_update_gizmos_menu() { gizmos_menu->clear(); @@ -6253,23 +6916,23 @@ void Node3DEditor::_init_grid() { Vector<Vector3> grid_points[3]; Vector<Vector3> grid_normals[3]; - Color primary_grid_color = EditorSettings::get_singleton()->get("editors/3d/primary_grid_color"); - Color secondary_grid_color = EditorSettings::get_singleton()->get("editors/3d/secondary_grid_color"); - int grid_size = EditorSettings::get_singleton()->get("editors/3d/grid_size"); - int primary_grid_steps = EditorSettings::get_singleton()->get("editors/3d/primary_grid_steps"); + Color primary_grid_color = EDITOR_GET("editors/3d/primary_grid_color"); + Color secondary_grid_color = EDITOR_GET("editors/3d/secondary_grid_color"); + int grid_size = EDITOR_GET("editors/3d/grid_size"); + int primary_grid_steps = EDITOR_GET("editors/3d/primary_grid_steps"); // Which grid planes are enabled? Which should we generate? - grid_enable[0] = grid_visible[0] = EditorSettings::get_singleton()->get("editors/3d/grid_xy_plane"); - grid_enable[1] = grid_visible[1] = EditorSettings::get_singleton()->get("editors/3d/grid_yz_plane"); - grid_enable[2] = grid_visible[2] = EditorSettings::get_singleton()->get("editors/3d/grid_xz_plane"); + grid_enable[0] = grid_visible[0] = EDITOR_GET("editors/3d/grid_xy_plane"); + grid_enable[1] = grid_visible[1] = EDITOR_GET("editors/3d/grid_yz_plane"); + grid_enable[2] = grid_visible[2] = EDITOR_GET("editors/3d/grid_xz_plane"); // Offsets division_level for bigger or smaller grids. // Default value is -0.2. -1.0 gives Blender-like behavior, 0.5 gives huge grids. - real_t division_level_bias = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_bias"); + real_t division_level_bias = EDITOR_GET("editors/3d/grid_division_level_bias"); // Default largest grid size is 8^2 when primary_grid_steps is 8 (64m apart, so primary grid lines are 512m apart). - int division_level_max = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_max"); + int division_level_max = EDITOR_GET("editors/3d/grid_division_level_max"); // Default smallest grid size is 1cm, 10^-2 (default value is -2). - int division_level_min = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_min"); + int division_level_min = EDITOR_GET("editors/3d/grid_division_level_min"); ERR_FAIL_COND_MSG(division_level_max < division_level_min, "The 3D grid's maximum division level cannot be lower than its minimum division level."); if (primary_grid_steps != 10) { // Log10 of 10 is 1. @@ -6294,7 +6957,7 @@ void Node3DEditor::_init_grid() { if (orthogonal) { camera_distance = camera->get_size() / 2.0; - Vector3 camera_direction = -camera->get_global_transform().get_basis().get_axis(2); + Vector3 camera_direction = -camera->get_global_transform().get_basis().get_column(2); Plane grid_plane = Plane(normal); Vector3 intersection; if (grid_plane.intersects_ray(camera_position, camera_direction, &intersection)) { @@ -6324,8 +6987,8 @@ void Node3DEditor::_init_grid() { fade_size = CLAMP(fade_size, min_fade_size, max_fade_size); real_t grid_fade_size = (grid_size - primary_grid_steps) * fade_size; - grid_mat[c]->set_shader_param("grid_size", grid_fade_size); - grid_mat[c]->set_shader_param("orthogonal", orthogonal); + grid_mat[c]->set_shader_parameter("grid_size", grid_fade_size); + grid_mat[c]->set_shader_parameter("orthogonal", orthogonal); // Cache these so we don't have to re-access memory. Vector<Vector3> &ref_grid = grid_points[c]; @@ -6369,8 +7032,8 @@ void Node3DEditor::_init_grid() { // Don't draw lines over the origin if it's enabled. if (!(origin_enabled && Math::is_zero_approx(position_a))) { - Vector3 line_bgn = Vector3(); - Vector3 line_end = Vector3(); + Vector3 line_bgn; + Vector3 line_end; line_bgn[a] = position_a; line_end[a] = position_a; line_bgn[b] = bgn_b; @@ -6385,8 +7048,8 @@ void Node3DEditor::_init_grid() { } if (!(origin_enabled && Math::is_zero_approx(position_b))) { - Vector3 line_bgn = Vector3(); - Vector3 line_end = Vector3(); + Vector3 line_bgn; + Vector3 line_end; line_bgn[b] = position_b; line_end[b] = position_b; line_bgn[a] = bgn_a; @@ -6417,6 +7080,7 @@ void Node3DEditor::_init_grid() { RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(grid_instance[c], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(grid_instance[c], 1 << Node3DEditorViewport::GIZMO_GRID_LAYER); RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); } } @@ -6435,7 +7099,7 @@ void Node3DEditor::_finish_grid() { } void Node3DEditor::update_grid() { - const Camera3D::Projection current_projection = viewports[0]->camera->get_projection(); + const Camera3D::ProjectionType current_projection = viewports[0]->camera->get_projection(); if (current_projection != grid_camera_last_update_perspective) { grid_init_draw = false; // redraw @@ -6510,8 +7174,8 @@ void Node3DEditor::_refresh_menu_icons() { } template <typename T> -Set<T *> _get_child_nodes(Node *parent_node) { - Set<T *> nodes = Set<T *>(); +HashSet<T *> _get_child_nodes(Node *parent_node) { + HashSet<T *> nodes = HashSet<T *>(); T *node = Node::cast_to<T>(parent_node); if (node) { nodes.insert(node); @@ -6519,54 +7183,59 @@ Set<T *> _get_child_nodes(Node *parent_node) { for (int i = 0; i < parent_node->get_child_count(); i++) { Node *child_node = parent_node->get_child(i); - Set<T *> child_nodes = _get_child_nodes<T>(child_node); - for (typename Set<T *>::Element *I = child_nodes.front(); I; I = I->next()) { - nodes.insert(I->get()); + HashSet<T *> child_nodes = _get_child_nodes<T>(child_node); + for (T *I : child_nodes) { + nodes.insert(I); } } return nodes; } -Set<RID> _get_physics_bodies_rid(Node *node) { - Set<RID> rids = Set<RID>(); +HashSet<RID> _get_physics_bodies_rid(Node *node) { + HashSet<RID> rids = HashSet<RID>(); PhysicsBody3D *pb = Node::cast_to<PhysicsBody3D>(node); if (pb) { rids.insert(pb->get_rid()); } - Set<PhysicsBody3D *> child_nodes = _get_child_nodes<PhysicsBody3D>(node); - for (Set<PhysicsBody3D *>::Element *I = child_nodes.front(); I; I = I->next()) { - rids.insert(I->get()->get_rid()); + HashSet<PhysicsBody3D *> child_nodes = _get_child_nodes<PhysicsBody3D>(node); + for (const PhysicsBody3D *I : child_nodes) { + rids.insert(I->get_rid()); } return rids; } void Node3DEditor::snap_selected_nodes_to_floor() { - List<Node *> &selection = editor_selection->get_selected_node_list(); + do_snap_selected_nodes_to_floor = true; +} + +void Node3DEditor::_snap_selected_nodes_to_floor() { + const List<Node *> &selection = editor_selection->get_selected_node_list(); Dictionary snap_data; for (Node *E : selection) { Node3D *sp = Object::cast_to<Node3D>(E); if (sp) { - Vector3 from = Vector3(); - Vector3 position_offset = Vector3(); + Vector3 from; + Vector3 position_offset; // Priorities for snapping to floor are CollisionShapes, VisualInstances and then origin - Set<VisualInstance3D *> vi = _get_child_nodes<VisualInstance3D>(sp); - Set<CollisionShape3D *> cs = _get_child_nodes<CollisionShape3D>(sp); + HashSet<VisualInstance3D *> vi = _get_child_nodes<VisualInstance3D>(sp); + HashSet<CollisionShape3D *> cs = _get_child_nodes<CollisionShape3D>(sp); bool found_valid_shape = false; if (cs.size()) { AABB aabb; - Set<CollisionShape3D *>::Element *I = cs.front(); - if (I->get()->get_shape().is_valid()) { - CollisionShape3D *collision_shape = cs.front()->get(); + HashSet<CollisionShape3D *>::Iterator I = cs.begin(); + if ((*I)->get_shape().is_valid()) { + CollisionShape3D *collision_shape = *cs.begin(); aabb = collision_shape->get_global_transform().xform(collision_shape->get_shape()->get_debug_mesh()->get_aabb()); found_valid_shape = true; } - for (I = I->next(); I; I = I->next()) { - CollisionShape3D *col_shape = I->get(); + + for (++I; I; ++I) { + CollisionShape3D *col_shape = *I; if (col_shape->get_shape().is_valid()) { aabb.merge_with(col_shape->get_global_transform().xform(col_shape->get_shape()->get_debug_mesh()->get_aabb())); found_valid_shape = true; @@ -6579,9 +7248,10 @@ void Node3DEditor::snap_selected_nodes_to_floor() { } } if (!found_valid_shape && vi.size()) { - AABB aabb = vi.front()->get()->get_transformed_aabb(); - for (Set<VisualInstance3D *>::Element *I = vi.front(); I; I = I->next()) { - aabb.merge_with(I->get()->get_transformed_aabb()); + VisualInstance3D *begin = *vi.begin(); + AABB aabb = begin->get_global_transform().xform(begin->get_aabb()); + for (const VisualInstance3D *I : vi) { + aabb.merge_with(I->get_global_transform().xform(I->get_aabb())); } Vector3 size = aabb.size * Vector3(0.5, 0.0, 0.5); from = aabb.position + size; @@ -6618,12 +7288,12 @@ void Node3DEditor::snap_selected_nodes_to_floor() { // For snapping to be performed, there must be solid geometry under at least one of the selected nodes. // We need to check this before snapping to register the undo/redo action only if needed. for (int i = 0; i < keys.size(); i++) { - Node *node = keys[i]; + Node *node = Object::cast_to<Node>(keys[i]); Node3D *sp = Object::cast_to<Node3D>(node); Dictionary d = snap_data[node]; Vector3 from = d["from"]; Vector3 to = from - Vector3(0.0, max_snap_height, 0.0); - Set<RID> excluded = _get_physics_bodies_rid(sp); + HashSet<RID> excluded = _get_physics_bodies_rid(sp); PhysicsDirectSpaceState3D::RayParameters ray_params; ray_params.from = from; @@ -6636,16 +7306,17 @@ void Node3DEditor::snap_selected_nodes_to_floor() { } if (snapped_to_floor) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Snap Nodes to Floor")); // Perform snapping if at least one node can be snapped for (int i = 0; i < keys.size(); i++) { - Node *node = keys[i]; + Node *node = Object::cast_to<Node>(keys[i]); Node3D *sp = Object::cast_to<Node3D>(node); Dictionary d = snap_data[node]; Vector3 from = d["from"]; Vector3 to = from - Vector3(0.0, max_snap_height, 0.0); - Set<RID> excluded = _get_physics_bodies_rid(sp); + HashSet<RID> excluded = _get_physics_bodies_rid(sp); PhysicsDirectSpaceState3D::RayParameters ray_params; ray_params.from = from; @@ -6671,7 +7342,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { } } -void Node3DEditor::unhandled_key_input(const Ref<InputEvent> &p_event) { +void Node3DEditor::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!is_visible_in_tree()) { @@ -6699,12 +7370,13 @@ void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) { Node *base = get_tree()->get_edited_scene_root(); if (!base) { // Create a root node so we can add child nodes to it. - EditorNode::get_singleton()->get_scene_tree_dock()->add_root_node(memnew(Node3D)); + SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D)); base = get_tree()->get_edited_scene_root(); } ERR_FAIL_COND(!base); Node *new_sun = preview_sun->duplicate(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Preview Sun to Scene")); undo_redo->add_do_method(base, "add_child", new_sun, true); // Move to the beginning of the scene tree since more "global" nodes @@ -6727,14 +7399,18 @@ void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) { Node *base = get_tree()->get_edited_scene_root(); if (!base) { // Create a root node so we can add child nodes to it. - EditorNode::get_singleton()->get_scene_tree_dock()->add_root_node(memnew(Node3D)); + SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D)); base = get_tree()->get_edited_scene_root(); } ERR_FAIL_COND(!base); WorldEnvironment *new_env = memnew(WorldEnvironment); new_env->set_environment(preview_environment->get_environment()->duplicate(true)); + if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { + new_env->set_camera_attributes(preview_environment->get_camera_attributes()->duplicate(true)); + } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Preview Environment to Scene")); undo_redo->add_do_method(base, "add_child", new_env, true); // Move to the beginning of the scene tree since more "global" nodes @@ -6747,19 +7423,19 @@ void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) { } void Node3DEditor::_update_theme() { - tool_button[Node3DEditor::TOOL_MODE_SELECT]->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_MODE_MOVE]->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_MODE_ROTATE]->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_MODE_SCALE]->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_MODE_LIST_SELECT]->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_LOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_UNLOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_GROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_UNGROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); - - tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_icon(get_theme_icon(SNAME("Object"), SNAME("EditorIcons"))); - tool_option_button[Node3DEditor::TOOL_OPT_USE_SNAP]->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); - tool_option_button[Node3DEditor::TOOL_OPT_OVERRIDE_CAMERA]->set_icon(get_theme_icon(SNAME("Camera3D"), SNAME("EditorIcons"))); + tool_button[TOOL_MODE_SELECT]->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_button[TOOL_MODE_MOVE]->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"))); + tool_button[TOOL_MODE_ROTATE]->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons"))); + tool_button[TOOL_MODE_SCALE]->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons"))); + tool_button[TOOL_MODE_LIST_SELECT]->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons"))); + tool_button[TOOL_LOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons"))); + tool_button[TOOL_UNLOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons"))); + tool_button[TOOL_GROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); + tool_button[TOOL_UNGROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); + + tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_icon(get_theme_icon(SNAME("Object"), SNAME("EditorIcons"))); + tool_option_button[TOOL_OPT_USE_SNAP]->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); + tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_icon(get_theme_icon(SNAME("Camera3D"), SNAME("EditorIcons"))); view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_theme_icon(SNAME("Panels1"), SNAME("EditorIcons"))); view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_theme_icon(SNAME("Panels2"), SNAME("EditorIcons"))); @@ -6768,12 +7444,18 @@ void Node3DEditor::_update_theme() { view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), get_theme_icon(SNAME("Panels3Alt"), SNAME("EditorIcons"))); view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), get_theme_icon(SNAME("Panels4"), SNAME("EditorIcons"))); - sun_button->set_icon(get_theme_icon(SNAME("DirectionalLight3D"), SNAME("EditorIcons"))); - environ_button->set_icon(get_theme_icon(SNAME("WorldEnvironment"), SNAME("EditorIcons"))); + sun_button->set_icon(get_theme_icon(SNAME("PreviewSun"), SNAME("EditorIcons"))); + environ_button->set_icon(get_theme_icon(SNAME("PreviewEnvironment"), SNAME("EditorIcons"))); sun_environ_settings->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); sun_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window"))); environ_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window"))); + + sun_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("color_picker_button_height"), SNAME("Editor")))); + environ_sky_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("color_picker_button_height"), SNAME("Editor")))); + environ_ground_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("color_picker_button_height"), SNAME("Editor")))); + + context_menu_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("ContextualToolbar"), SNAME("EditorStyles"))); } void Node3DEditor::_notification(int p_what) { @@ -6785,17 +7467,20 @@ void Node3DEditor::_notification(int p_what) { get_tree()->connect("node_removed", callable_mp(this, &Node3DEditor::_node_removed)); get_tree()->connect("node_added", callable_mp(this, &Node3DEditor::_node_added)); - EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons)); + SceneTreeDock::get_singleton()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons)); editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_selection_changed)); - editor->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(false)); - editor->connect("play_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(true)); + EditorNode::get_singleton()->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button).bind(false)); + EditorNode::get_singleton()->connect("play_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button).bind(true)); _update_preview_environment(); sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size()); environ_state->set_custom_minimum_size(environ_vb->get_combined_minimum_size()); + + EditorNode::get_singleton()->connect("project_settings_changed", callable_mp(this, &Node3DEditor::update_all_gizmos).bind(Variant())); } break; + case NOTIFICATION_ENTER_TREE: { _update_theme(); _register_all_gizmos(); @@ -6803,21 +7488,24 @@ void Node3DEditor::_notification(int p_what) { _init_indicators(); update_all_gizmos(); } break; + case NOTIFICATION_EXIT_TREE: { _finish_indicators(); } break; + case NOTIFICATION_THEME_CHANGED: { _update_theme(); _update_gizmos_menu_theme(); - _update_context_menu_stylebox(); sun_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window"))); environ_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window"))); } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { // Update grid color by rebuilding grid. _finish_grid(); _init_grid(); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible() && tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->is_pressed()) { EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton(); @@ -6826,6 +7514,13 @@ void Node3DEditor::_notification(int p_what) { tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_pressed(false); } } break; + + case NOTIFICATION_PHYSICS_PROCESS: { + if (do_snap_selected_nodes_to_floor) { + _snap_selected_nodes_to_floor(); + do_snap_selected_nodes_to_floor = false; + } + } } } @@ -6858,11 +7553,11 @@ Vector<int> Node3DEditor::get_subgizmo_selection() { } void Node3DEditor::add_control_to_menu_panel(Control *p_control) { - hbc_context_menu->add_child(p_control); + context_menu_hbox->add_child(p_control); } void Node3DEditor::remove_control_from_menu_panel(Control *p_control) { - hbc_context_menu->remove_child(p_control); + context_menu_hbox->remove_child(p_control); } void Node3DEditor::set_can_preview(Camera3D *p_preview) { @@ -6875,8 +7570,46 @@ VSplitContainer *Node3DEditor::get_shader_split() { return shader_split; } -HSplitContainer *Node3DEditor::get_palette_split() { - return palette_split; +void Node3DEditor::add_control_to_left_panel(Control *p_control) { + left_panel_split->add_child(p_control); + left_panel_split->move_child(p_control, 0); +} + +void Node3DEditor::add_control_to_right_panel(Control *p_control) { + right_panel_split->add_child(p_control); + right_panel_split->move_child(p_control, 1); +} + +void Node3DEditor::remove_control_from_left_panel(Control *p_control) { + left_panel_split->remove_child(p_control); +} + +void Node3DEditor::remove_control_from_right_panel(Control *p_control) { + right_panel_split->remove_child(p_control); +} + +void Node3DEditor::move_control_to_left_panel(Control *p_control) { + ERR_FAIL_NULL(p_control); + if (p_control->get_parent() == left_panel_split) { + return; + } + + ERR_FAIL_COND(p_control->get_parent() != right_panel_split); + right_panel_split->remove_child(p_control); + + add_control_to_left_panel(p_control); +} + +void Node3DEditor::move_control_to_right_panel(Control *p_control) { + ERR_FAIL_NULL(p_control); + if (p_control->get_parent() == right_panel_split) { + return; + } + + ERR_FAIL_COND(p_control->get_parent() != left_panel_split); + left_panel_split->remove_child(p_control); + + add_control_to_right_panel(p_control); } void Node3DEditor::_request_gizmo(Object *p_obj) { @@ -6887,7 +7620,8 @@ void Node3DEditor::_request_gizmo(Object *p_obj) { bool is_selected = (sp == selected); - if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_ancestor_of(sp)))) { + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + if (edited_scene && (sp == edited_scene || (sp->get_owner() && edited_scene->is_ancestor_of(sp)))) { for (int i = 0; i < gizmo_plugins_by_priority.size(); ++i) { Ref<EditorNode3DGizmo> seg = gizmo_plugins_by_priority.write[i]->get_gizmo(sp); @@ -6899,7 +7633,16 @@ void Node3DEditor::_request_gizmo(Object *p_obj) { } } } - sp->update_gizmos(); + if (!sp->get_gizmos().is_empty()) { + sp->update_gizmos(); + } + } +} + +void Node3DEditor::_request_gizmo_for_id(ObjectID p_id) { + Node3D *node = Object::cast_to<Node3D>(ObjectDB::get_instance(p_id)); + if (node) { + _request_gizmo(node); } } @@ -6978,7 +7721,7 @@ void Node3DEditor::_toggle_maximize_view(Object *p_viewport) { if (!maximized) { for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) { if (i == (uint32_t)index) { - viewports[i]->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + viewports[i]->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); } else { viewports[i]->hide(); } @@ -7053,10 +7796,12 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<AudioListener3DGizmoPlugin>(memnew(AudioListener3DGizmoPlugin))); add_gizmo_plugin(Ref<MeshInstance3DGizmoPlugin>(memnew(MeshInstance3DGizmoPlugin))); add_gizmo_plugin(Ref<OccluderInstance3DGizmoPlugin>(memnew(OccluderInstance3DGizmoPlugin))); - add_gizmo_plugin(Ref<SoftDynamicBody3DGizmoPlugin>(memnew(SoftDynamicBody3DGizmoPlugin))); + add_gizmo_plugin(Ref<SoftBody3DGizmoPlugin>(memnew(SoftBody3DGizmoPlugin))); add_gizmo_plugin(Ref<Sprite3DGizmoPlugin>(memnew(Sprite3DGizmoPlugin))); - add_gizmo_plugin(Ref<Position3DGizmoPlugin>(memnew(Position3DGizmoPlugin))); + add_gizmo_plugin(Ref<Label3DGizmoPlugin>(memnew(Label3DGizmoPlugin))); + add_gizmo_plugin(Ref<Marker3DGizmoPlugin>(memnew(Marker3DGizmoPlugin))); add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin))); + add_gizmo_plugin(Ref<ShapeCast3DGizmoPlugin>(memnew(ShapeCast3DGizmoPlugin))); add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin))); add_gizmo_plugin(Ref<VehicleWheel3DGizmoPlugin>(memnew(VehicleWheel3DGizmoPlugin))); add_gizmo_plugin(Ref<VisibleOnScreenNotifier3DGizmoPlugin>(memnew(VisibleOnScreenNotifier3DGizmoPlugin))); @@ -7071,6 +7816,7 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<CollisionObject3DGizmoPlugin>(memnew(CollisionObject3DGizmoPlugin))); add_gizmo_plugin(Ref<CollisionShape3DGizmoPlugin>(memnew(CollisionShape3DGizmoPlugin))); add_gizmo_plugin(Ref<CollisionPolygon3DGizmoPlugin>(memnew(CollisionPolygon3DGizmoPlugin))); + add_gizmo_plugin(Ref<NavigationLink3DGizmoPlugin>(memnew(NavigationLink3DGizmoPlugin))); add_gizmo_plugin(Ref<NavigationRegion3DGizmoPlugin>(memnew(NavigationRegion3DGizmoPlugin))); add_gizmo_plugin(Ref<Joint3DGizmoPlugin>(memnew(Joint3DGizmoPlugin))); add_gizmo_plugin(Ref<PhysicalBone3DGizmoPlugin>(memnew(PhysicalBone3DGizmoPlugin))); @@ -7080,6 +7826,7 @@ void Node3DEditor::_register_all_gizmos() { void Node3DEditor::_bind_methods() { ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data); ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo); + ClassDB::bind_method("_request_gizmo_for_id", &Node3DEditor::_request_gizmo_for_id); ClassDB::bind_method("_set_subgizmo_selection", &Node3DEditor::_set_subgizmo_selection); ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection); ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons); @@ -7116,11 +7863,11 @@ void Node3DEditor::clear() { void Node3DEditor::_sun_direction_draw() { sun_direction->draw_rect(Rect2(Vector2(), sun_direction->get_size()), Color(1, 1, 1, 1)); - Vector3 z_axis = preview_sun->get_transform().basis.get_axis(Vector3::AXIS_Z); + Vector3 z_axis = preview_sun->get_transform().basis.get_column(Vector3::AXIS_Z); z_axis = get_editor_viewport(0)->camera->get_camera_transform().basis.xform_inv(z_axis); - sun_direction_material->set_shader_param("sun_direction", Vector3(z_axis.x, -z_axis.y, z_axis.z)); + sun_direction_material->set_shader_parameter("sun_direction", Vector3(z_axis.x, -z_axis.y, z_axis.z)); Color color = sun_color->get_pick_color() * sun_energy->get_value(); - sun_direction_material->set_shader_param("sun_color", Vector3(color.r, color.g, color.b)); + sun_direction_material->set_shader_parameter("sun_color", Vector3(color.r, color.g, color.b)); } void Node3DEditor::_preview_settings_changed() { @@ -7130,16 +7877,16 @@ void Node3DEditor::_preview_settings_changed() { { // preview sun Transform3D t; - t.basis = Basis(Vector3(sun_rotation.x, sun_rotation.y, 0)); + t.basis = Basis::from_euler(Vector3(sun_rotation.x, sun_rotation.y, 0)); preview_sun->set_transform(t); - sun_direction->update(); + sun_direction->queue_redraw(); preview_sun->set_param(Light3D::PARAM_ENERGY, sun_energy->get_value()); preview_sun->set_param(Light3D::PARAM_SHADOW_MAX_DISTANCE, sun_max_distance->get_value()); preview_sun->set_color(sun_color->get_pick_color()); } { //preview env - sky_material->set_sky_energy(environ_energy->get_value()); + sky_material->set_sky_energy_multiplier(environ_energy->get_value()); Color hz_color = environ_sky_color->get_pick_color().lerp(environ_ground_color->get_pick_color(), 0.5).lerp(Color(1, 1, 1), 0.5); sky_material->set_sky_top_color(environ_sky_color->get_pick_color()); sky_material->set_sky_horizon_color(hz_color); @@ -7162,19 +7909,19 @@ void Node3DEditor::_load_default_preview_settings() { // On any not-tidally-locked planet, a sun would have an angular altitude // of 60 degrees as the average of all points on the sphere at noon. // The azimuth choice is arbitrary, but ideally shouldn't be on an axis. - sun_rotation = Vector2(-Math::deg2rad(60.0), Math::deg2rad(150.0)); + sun_rotation = Vector2(-Math::deg_to_rad(60.0), Math::deg_to_rad(150.0)); - sun_angle_altitude->set_value(-Math::rad2deg(sun_rotation.x)); - sun_angle_azimuth->set_value(180.0 - Math::rad2deg(sun_rotation.y)); - sun_direction->update(); - environ_sky_color->set_pick_color(Color::hex(0x91b2ceff)); - environ_ground_color->set_pick_color(Color::hex(0x1f1f21ff)); + sun_angle_altitude->set_value(-Math::rad_to_deg(sun_rotation.x)); + sun_angle_azimuth->set_value(180.0 - Math::rad_to_deg(sun_rotation.y)); + sun_direction->queue_redraw(); + environ_sky_color->set_pick_color(Color(0.385, 0.454, 0.55)); + environ_ground_color->set_pick_color(Color(0.2, 0.169, 0.133)); environ_energy->set_value(1.0); environ_glow_button->set_pressed(true); environ_tonemap_button->set_pressed(true); environ_ao_button->set_pressed(false); environ_gi_button->set_pressed(false); - sun_max_distance->set_value(250); + sun_max_distance->set_value(100); sun_color->set_pick_color(Color(1, 1, 1)); sun_energy->set_value(1.0); @@ -7183,7 +7930,7 @@ void Node3DEditor::_load_default_preview_settings() { } void Node3DEditor::_update_preview_environment() { - bool disable_light = directional_light_count > 0 || sun_button->is_pressed(); + bool disable_light = directional_light_count > 0 || !sun_button->is_pressed(); sun_button->set_disabled(directional_light_count > 0); @@ -7192,6 +7939,7 @@ void Node3DEditor::_update_preview_environment() { preview_sun->get_parent()->remove_child(preview_sun); sun_state->show(); sun_vb->hide(); + preview_sun_dangling = true; } if (directional_light_count > 0) { @@ -7205,13 +7953,14 @@ void Node3DEditor::_update_preview_environment() { add_child(preview_sun, true); sun_state->hide(); sun_vb->show(); + preview_sun_dangling = false; } } - sun_angle_altitude->set_value(-Math::rad2deg(sun_rotation.x)); - sun_angle_azimuth->set_value(180.0 - Math::rad2deg(sun_rotation.y)); + sun_angle_altitude->set_value(-Math::rad_to_deg(sun_rotation.x)); + sun_angle_azimuth->set_value(180.0 - Math::rad_to_deg(sun_rotation.y)); - bool disable_env = world_env_count > 0 || environ_button->is_pressed(); + bool disable_env = world_env_count > 0 || !environ_button->is_pressed(); environ_button->set_disabled(world_env_count > 0); @@ -7220,6 +7969,7 @@ void Node3DEditor::_update_preview_environment() { preview_environment->get_parent()->remove_child(preview_environment); environ_state->show(); environ_vb->hide(); + preview_env_dangling = true; } if (world_env_count > 0) { environ_state->set_text(TTR("Scene contains\nWorldEnvironment.\nPreview disabled.")); @@ -7232,40 +7982,39 @@ void Node3DEditor::_update_preview_environment() { add_child(preview_environment); environ_state->hide(); environ_vb->show(); + preview_env_dangling = false; } } } void Node3DEditor::_sun_direction_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { + if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { sun_rotation.x += mm->get_relative().y * (0.02 * EDSCALE); sun_rotation.y -= mm->get_relative().x * (0.02 * EDSCALE); sun_rotation.x = CLAMP(sun_rotation.x, -Math_TAU / 4, Math_TAU / 4); - sun_angle_altitude->set_value(-Math::rad2deg(sun_rotation.x)); - sun_angle_azimuth->set_value(180.0 - Math::rad2deg(sun_rotation.y)); + sun_angle_altitude->set_value(-Math::rad_to_deg(sun_rotation.x)); + sun_angle_azimuth->set_value(180.0 - Math::rad_to_deg(sun_rotation.y)); _preview_settings_changed(); } } void Node3DEditor::_sun_direction_angle_set() { - sun_rotation.x = Math::deg2rad(-sun_angle_altitude->get_value()); - sun_rotation.y = Math::deg2rad(180.0 - sun_angle_azimuth->get_value()); + sun_rotation.x = Math::deg_to_rad(-sun_angle_altitude->get_value()); + sun_rotation.y = Math::deg_to_rad(180.0 - sun_angle_azimuth->get_value()); _preview_settings_changed(); } -Node3DEditor::Node3DEditor(EditorNode *p_editor) { +Node3DEditor::Node3DEditor() { gizmo.visible = true; gizmo.scale = 1.0; viewport_environment = Ref<Environment>(memnew(Environment)); - undo_redo = p_editor->get_undo_redo(); VBoxContainer *vbc = this; custom_camera = nullptr; singleton = this; - editor = p_editor; - editor_selection = editor->get_editor_selection(); + editor_selection = EditorNode::get_singleton()->get_editor_selection(); editor_selection->add_editor_plugin(this); snap_enabled = false; @@ -7274,163 +8023,157 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { camera_override_viewport_id = 0; - hbc_menu = memnew(HBoxContainer); - vbc->add_child(hbc_menu); + // A fluid container for all toolbars. + HFlowContainer *main_flow = memnew(HFlowContainer); + vbc->add_child(main_flow); + + // Main toolbars. + HBoxContainer *main_menu_hbox = memnew(HBoxContainer); + main_flow->add_child(main_menu_hbox); - Vector<Variant> button_binds; - button_binds.resize(1); String sct; // Add some margin to the left for better aesthetics. // This prevents the first button's hover/pressed effect from "touching" the panel's border, // which looks ugly. Control *margin_left = memnew(Control); - hbc_menu->add_child(margin_left); + main_menu_hbox->add_child(margin_left); margin_left->set_custom_minimum_size(Size2(2, 0) * EDSCALE); tool_button[TOOL_MODE_SELECT] = memnew(Button); - hbc_menu->add_child(tool_button[TOOL_MODE_SELECT]); + main_menu_hbox->add_child(tool_button[TOOL_MODE_SELECT]); tool_button[TOOL_MODE_SELECT]->set_toggle_mode(true); tool_button[TOOL_MODE_SELECT]->set_flat(true); tool_button[TOOL_MODE_SELECT]->set_pressed(true); - button_binds.write[0] = MENU_TOOL_SELECT; - tool_button[TOOL_MODE_SELECT]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); + tool_button[TOOL_MODE_SELECT]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_SELECT)); tool_button[TOOL_MODE_SELECT]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_select", TTR("Select Mode"), Key::Q)); tool_button[TOOL_MODE_SELECT]->set_shortcut_context(this); - tool_button[TOOL_MODE_SELECT]->set_tooltip(keycode_get_string((Key)KeyModifierMask::CMD) + TTR("Drag: Rotate selected node around pivot.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.")); - hbc_menu->add_child(memnew(VSeparator)); + tool_button[TOOL_MODE_SELECT]->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Drag: Rotate selected node around pivot.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.")); + main_menu_hbox->add_child(memnew(VSeparator)); tool_button[TOOL_MODE_MOVE] = memnew(Button); - hbc_menu->add_child(tool_button[TOOL_MODE_MOVE]); + main_menu_hbox->add_child(tool_button[TOOL_MODE_MOVE]); tool_button[TOOL_MODE_MOVE]->set_toggle_mode(true); tool_button[TOOL_MODE_MOVE]->set_flat(true); - button_binds.write[0] = MENU_TOOL_MOVE; - tool_button[TOOL_MODE_MOVE]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); + + tool_button[TOOL_MODE_MOVE]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_MOVE)); tool_button[TOOL_MODE_MOVE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_move", TTR("Move Mode"), Key::W)); tool_button[TOOL_MODE_MOVE]->set_shortcut_context(this); tool_button[TOOL_MODE_ROTATE] = memnew(Button); - hbc_menu->add_child(tool_button[TOOL_MODE_ROTATE]); + main_menu_hbox->add_child(tool_button[TOOL_MODE_ROTATE]); tool_button[TOOL_MODE_ROTATE]->set_toggle_mode(true); tool_button[TOOL_MODE_ROTATE]->set_flat(true); - button_binds.write[0] = MENU_TOOL_ROTATE; - tool_button[TOOL_MODE_ROTATE]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); + tool_button[TOOL_MODE_ROTATE]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_ROTATE)); tool_button[TOOL_MODE_ROTATE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_rotate", TTR("Rotate Mode"), Key::E)); tool_button[TOOL_MODE_ROTATE]->set_shortcut_context(this); tool_button[TOOL_MODE_SCALE] = memnew(Button); - hbc_menu->add_child(tool_button[TOOL_MODE_SCALE]); + main_menu_hbox->add_child(tool_button[TOOL_MODE_SCALE]); tool_button[TOOL_MODE_SCALE]->set_toggle_mode(true); tool_button[TOOL_MODE_SCALE]->set_flat(true); - button_binds.write[0] = MENU_TOOL_SCALE; - tool_button[TOOL_MODE_SCALE]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); + tool_button[TOOL_MODE_SCALE]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_SCALE)); tool_button[TOOL_MODE_SCALE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_scale", TTR("Scale Mode"), Key::R)); tool_button[TOOL_MODE_SCALE]->set_shortcut_context(this); - hbc_menu->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); tool_button[TOOL_MODE_LIST_SELECT] = memnew(Button); - hbc_menu->add_child(tool_button[TOOL_MODE_LIST_SELECT]); + main_menu_hbox->add_child(tool_button[TOOL_MODE_LIST_SELECT]); tool_button[TOOL_MODE_LIST_SELECT]->set_toggle_mode(true); tool_button[TOOL_MODE_LIST_SELECT]->set_flat(true); - button_binds.write[0] = MENU_TOOL_LIST_SELECT; - tool_button[TOOL_MODE_LIST_SELECT]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); - tool_button[TOOL_MODE_LIST_SELECT]->set_tooltip(TTR("Show list of selectable nodes at position clicked.")); + tool_button[TOOL_MODE_LIST_SELECT]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_LIST_SELECT)); + tool_button[TOOL_MODE_LIST_SELECT]->set_tooltip_text(TTR("Show list of selectable nodes at position clicked.")); tool_button[TOOL_LOCK_SELECTED] = memnew(Button); - hbc_menu->add_child(tool_button[TOOL_LOCK_SELECTED]); + main_menu_hbox->add_child(tool_button[TOOL_LOCK_SELECTED]); tool_button[TOOL_LOCK_SELECTED]->set_flat(true); - button_binds.write[0] = MENU_LOCK_SELECTED; - tool_button[TOOL_LOCK_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); - tool_button[TOOL_LOCK_SELECTED]->set_tooltip(TTR("Lock selected node, preventing selection and movement.")); + tool_button[TOOL_LOCK_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_LOCK_SELECTED)); + tool_button[TOOL_LOCK_SELECTED]->set_tooltip_text(TTR("Lock selected node, preventing selection and movement.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - tool_button[TOOL_LOCK_SELECTED]->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KeyModifierMask::CMD | Key::L)); + tool_button[TOOL_LOCK_SELECTED]->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::L)); tool_button[TOOL_UNLOCK_SELECTED] = memnew(Button); - hbc_menu->add_child(tool_button[TOOL_UNLOCK_SELECTED]); + main_menu_hbox->add_child(tool_button[TOOL_UNLOCK_SELECTED]); tool_button[TOOL_UNLOCK_SELECTED]->set_flat(true); - button_binds.write[0] = MENU_UNLOCK_SELECTED; - tool_button[TOOL_UNLOCK_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); - tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip(TTR("Unlock selected node, allowing selection and movement.")); + tool_button[TOOL_UNLOCK_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNLOCK_SELECTED)); + tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip_text(TTR("Unlock selected node, allowing selection and movement.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - tool_button[TOOL_UNLOCK_SELECTED]->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::L)); + tool_button[TOOL_UNLOCK_SELECTED]->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::L)); tool_button[TOOL_GROUP_SELECTED] = memnew(Button); - hbc_menu->add_child(tool_button[TOOL_GROUP_SELECTED]); + main_menu_hbox->add_child(tool_button[TOOL_GROUP_SELECTED]); tool_button[TOOL_GROUP_SELECTED]->set_flat(true); - button_binds.write[0] = MENU_GROUP_SELECTED; - tool_button[TOOL_GROUP_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); - tool_button[TOOL_GROUP_SELECTED]->set_tooltip(TTR("Makes sure the object's children are not selectable.")); + tool_button[TOOL_GROUP_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_GROUP_SELECTED)); + tool_button[TOOL_GROUP_SELECTED]->set_tooltip_text(TTR("Make selected node's children not selectable.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - tool_button[TOOL_GROUP_SELECTED]->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD | Key::G)); + tool_button[TOOL_GROUP_SELECTED]->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::G)); tool_button[TOOL_UNGROUP_SELECTED] = memnew(Button); - hbc_menu->add_child(tool_button[TOOL_UNGROUP_SELECTED]); + main_menu_hbox->add_child(tool_button[TOOL_UNGROUP_SELECTED]); tool_button[TOOL_UNGROUP_SELECTED]->set_flat(true); - button_binds.write[0] = MENU_UNGROUP_SELECTED; - tool_button[TOOL_UNGROUP_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); - tool_button[TOOL_UNGROUP_SELECTED]->set_tooltip(TTR("Restores the object's children's ability to be selected.")); + tool_button[TOOL_UNGROUP_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNGROUP_SELECTED)); + tool_button[TOOL_UNGROUP_SELECTED]->set_tooltip_text(TTR("Make selected node's children selectable.")); // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. - tool_button[TOOL_UNGROUP_SELECTED]->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::G)); + tool_button[TOOL_UNGROUP_SELECTED]->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G)); - hbc_menu->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); tool_option_button[TOOL_OPT_LOCAL_COORDS] = memnew(Button); - hbc_menu->add_child(tool_option_button[TOOL_OPT_LOCAL_COORDS]); + main_menu_hbox->add_child(tool_option_button[TOOL_OPT_LOCAL_COORDS]); tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_toggle_mode(true); tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_flat(true); - button_binds.write[0] = MENU_TOOL_LOCAL_COORDS; - tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled), button_binds); + tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_LOCAL_COORDS)); tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut(ED_SHORTCUT("spatial_editor/local_coords", TTR("Use Local Space"), Key::T)); tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut_context(this); tool_option_button[TOOL_OPT_USE_SNAP] = memnew(Button); - hbc_menu->add_child(tool_option_button[TOOL_OPT_USE_SNAP]); + main_menu_hbox->add_child(tool_option_button[TOOL_OPT_USE_SNAP]); tool_option_button[TOOL_OPT_USE_SNAP]->set_toggle_mode(true); tool_option_button[TOOL_OPT_USE_SNAP]->set_flat(true); - button_binds.write[0] = MENU_TOOL_USE_SNAP; - tool_option_button[TOOL_OPT_USE_SNAP]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled), button_binds); + tool_option_button[TOOL_OPT_USE_SNAP]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_SNAP)); tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut(ED_SHORTCUT("spatial_editor/snap", TTR("Use Snap"), Key::Y)); tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut_context(this); - hbc_menu->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); tool_option_button[TOOL_OPT_OVERRIDE_CAMERA] = memnew(Button); - hbc_menu->add_child(tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]); + main_menu_hbox->add_child(tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]); tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_toggle_mode(true); tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_flat(true); tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_disabled(true); - button_binds.write[0] = MENU_TOOL_OVERRIDE_CAMERA; - tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled), button_binds); + tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_OVERRIDE_CAMERA)); _update_camera_override_button(false); - hbc_menu->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); sun_button = memnew(Button); - sun_button->set_tooltip(TTR("Toggle preview sunlight.\nIf a DirectionalLight3D node is added to the scene, preview sunlight is disabled.")); + sun_button->set_tooltip_text(TTR("Toggle preview sunlight.\nIf a DirectionalLight3D node is added to the scene, preview sunlight is disabled.")); sun_button->set_toggle_mode(true); sun_button->set_flat(true); - sun_button->connect("pressed", callable_mp(this, &Node3DEditor::_update_preview_environment), varray(), CONNECT_DEFERRED); - sun_button->set_disabled(true); + sun_button->connect("pressed", callable_mp(this, &Node3DEditor::_update_preview_environment), CONNECT_DEFERRED); + // Preview is enabled by default - ensure this applies on editor startup when there is no state yet. + sun_button->set_pressed(true); - hbc_menu->add_child(sun_button); + main_menu_hbox->add_child(sun_button); environ_button = memnew(Button); - environ_button->set_tooltip(TTR("Toggle preview environment.\nIf a WorldEnvironment node is added to the scene, preview environment is disabled.")); + environ_button->set_tooltip_text(TTR("Toggle preview environment.\nIf a WorldEnvironment node is added to the scene, preview environment is disabled.")); environ_button->set_toggle_mode(true); environ_button->set_flat(true); - environ_button->connect("pressed", callable_mp(this, &Node3DEditor::_update_preview_environment), varray(), CONNECT_DEFERRED); - environ_button->set_disabled(true); + environ_button->connect("pressed", callable_mp(this, &Node3DEditor::_update_preview_environment), CONNECT_DEFERRED); + // Preview is enabled by default - ensure this applies on editor startup when there is no state yet. + environ_button->set_pressed(true); - hbc_menu->add_child(environ_button); + main_menu_hbox->add_child(environ_button); sun_environ_settings = memnew(Button); - sun_environ_settings->set_tooltip(TTR("Edit Sun and Environment settings.")); + sun_environ_settings->set_tooltip_text(TTR("Edit Sun and Environment settings.")); sun_environ_settings->set_flat(true); sun_environ_settings->connect("pressed", callable_mp(this, &Node3DEditor::_sun_environ_settings_pressed)); - hbc_menu->add_child(sun_environ_settings); + main_menu_hbox->add_child(sun_environ_settings); - hbc_menu->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); // Drag and drop support; preview_node = memnew(Node3D); @@ -7451,12 +8194,12 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { ED_SHORTCUT("spatial_editor/insert_anim_key", TTR("Insert Animation Key"), Key::K); ED_SHORTCUT("spatial_editor/focus_origin", TTR("Focus Origin"), Key::O); ED_SHORTCUT("spatial_editor/focus_selection", TTR("Focus Selection"), Key::F); - ED_SHORTCUT("spatial_editor/align_transform_with_view", TTR("Align Transform with View"), KeyModifierMask::ALT + KeyModifierMask::CMD + Key::M); - ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTR("Align Rotation with View"), KeyModifierMask::ALT + KeyModifierMask::CMD + Key::F); + ED_SHORTCUT("spatial_editor/align_transform_with_view", TTR("Align Transform with View"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::M); + ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTR("Align Rotation with View"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::F); ED_SHORTCUT("spatial_editor/freelook_toggle", TTR("Toggle Freelook"), KeyModifierMask::SHIFT + Key::F); - ED_SHORTCUT("spatial_editor/decrease_fov", TTR("Decrease Field of View"), KeyModifierMask::CMD + Key::EQUAL); // Usually direct access key for `KEY_PLUS`. - ED_SHORTCUT("spatial_editor/increase_fov", TTR("Increase Field of View"), KeyModifierMask::CMD + Key::MINUS); - ED_SHORTCUT("spatial_editor/reset_fov", TTR("Reset Field of View to Default"), KeyModifierMask::CMD + Key::KEY_0); + ED_SHORTCUT("spatial_editor/decrease_fov", TTR("Decrease Field of View"), KeyModifierMask::CMD_OR_CTRL + Key::EQUAL); // Usually direct access key for `KEY_PLUS`. + ED_SHORTCUT("spatial_editor/increase_fov", TTR("Increase Field of View"), KeyModifierMask::CMD_OR_CTRL + Key::MINUS); + ED_SHORTCUT("spatial_editor/reset_fov", TTR("Reset Field of View to Default"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_0); PopupMenu *p; @@ -7464,7 +8207,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { transform_menu->set_text(TTR("Transform")); transform_menu->set_switch_on_hover(true); transform_menu->set_shortcut_context(this); - hbc_menu->add_child(transform_menu); + main_menu_hbox->add_child(transform_menu); p = transform_menu->get_popup(); p->add_shortcut(ED_SHORTCUT("spatial_editor/snap_to_floor", TTR("Snap Object to Floor"), Key::PAGEDOWN), MENU_SNAP_TO_FLOOR); @@ -7476,35 +8219,32 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { p->connect("id_pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed)); view_menu = memnew(MenuButton); + // TRANSLATORS: Noun, name of the 2D/3D View menus. view_menu->set_text(TTR("View")); view_menu->set_switch_on_hover(true); view_menu->set_shortcut_context(this); - hbc_menu->add_child(view_menu); + main_menu_hbox->add_child(view_menu); - hbc_menu->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); - context_menu_container = memnew(PanelContainer); - hbc_context_menu = memnew(HBoxContainer); - context_menu_container->add_child(hbc_context_menu); - // Use a custom stylebox to make contextual menu items stand out from the rest. - // This helps with editor usability as contextual menu items change when selecting nodes, - // even though it may not be immediately obvious at first. - hbc_menu->add_child(context_menu_container); - _update_context_menu_stylebox(); + context_menu_panel = memnew(PanelContainer); + context_menu_hbox = memnew(HBoxContainer); + context_menu_panel->add_child(context_menu_hbox); + main_flow->add_child(context_menu_panel); // Get the view menu popup and have it stay open when a checkable item is selected p = view_menu->get_popup(); p->set_hide_on_checkable_item_selection(false); accept = memnew(AcceptDialog); - editor->get_gui_base()->add_child(accept); - - p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTR("1 Viewport"), KeyModifierMask::CMD + Key::KEY_1), MENU_VIEW_USE_1_VIEWPORT); - p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTR("2 Viewports"), KeyModifierMask::CMD + Key::KEY_2), MENU_VIEW_USE_2_VIEWPORTS); - p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports_alt", TTR("2 Viewports (Alt)"), KeyModifierMask::ALT + KeyModifierMask::CMD + Key::KEY_2), MENU_VIEW_USE_2_VIEWPORTS_ALT); - p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTR("3 Viewports"), KeyModifierMask::CMD + Key::KEY_3), MENU_VIEW_USE_3_VIEWPORTS); - p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports_alt", TTR("3 Viewports (Alt)"), KeyModifierMask::ALT + KeyModifierMask::CMD + Key::KEY_3), MENU_VIEW_USE_3_VIEWPORTS_ALT); - p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTR("4 Viewports"), KeyModifierMask::CMD + Key::KEY_4), MENU_VIEW_USE_4_VIEWPORTS); + EditorNode::get_singleton()->get_gui_base()->add_child(accept); + + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTR("1 Viewport"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_1), MENU_VIEW_USE_1_VIEWPORT); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTR("2 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_2), MENU_VIEW_USE_2_VIEWPORTS); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports_alt", TTR("2 Viewports (Alt)"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::KEY_2), MENU_VIEW_USE_2_VIEWPORTS_ALT); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTR("3 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_3), MENU_VIEW_USE_3_VIEWPORTS); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports_alt", TTR("3 Viewports (Alt)"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::KEY_3), MENU_VIEW_USE_3_VIEWPORTS_ALT); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTR("4 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_4), MENU_VIEW_USE_4_VIEWPORTS); p->add_separator(); p->add_submenu_item(TTR("Gizmos"), "GizmosMenu"); @@ -7529,18 +8269,22 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { /* REST OF MENU */ - palette_split = memnew(HSplitContainer); - palette_split->set_v_size_flags(SIZE_EXPAND_FILL); - vbc->add_child(palette_split); + left_panel_split = memnew(HSplitContainer); + left_panel_split->set_v_size_flags(SIZE_EXPAND_FILL); + vbc->add_child(left_panel_split); + + right_panel_split = memnew(HSplitContainer); + right_panel_split->set_v_size_flags(SIZE_EXPAND_FILL); + left_panel_split->add_child(right_panel_split); shader_split = memnew(VSplitContainer); shader_split->set_h_size_flags(SIZE_EXPAND_FILL); - palette_split->add_child(shader_split); + right_panel_split->add_child(shader_split); viewport_base = memnew(Node3DEditorViewportContainer); shader_split->add_child(viewport_base); viewport_base->set_v_size_flags(SIZE_EXPAND_FILL); for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) { - viewports[i] = memnew(Node3DEditorViewport(this, editor, i)); + viewports[i] = memnew(Node3DEditorViewport(this, i)); viewports[i]->connect("toggle_maximize_view", callable_mp(this, &Node3DEditor::_toggle_maximize_view)); viewports[i]->connect("clicked", callable_mp(this, &Node3DEditor::_update_camera_override_viewport)); viewports[i]->assign_pending_data_pointers(preview_node, &preview_bounds, accept); @@ -7563,12 +8307,15 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { snap_dialog->add_child(snap_dialog_vbc); snap_translate = memnew(LineEdit); + snap_translate->set_select_all_on_focus(true); snap_dialog_vbc->add_margin_child(TTR("Translate Snap:"), snap_translate); snap_rotate = memnew(LineEdit); + snap_rotate->set_select_all_on_focus(true); snap_dialog_vbc->add_margin_child(TTR("Rotate Snap (deg.):"), snap_rotate); snap_scale = memnew(LineEdit); + snap_scale->set_select_all_on_focus(true); snap_dialog_vbc->add_margin_child(TTR("Scale Snap (%):"), snap_scale); _snap_update(); @@ -7587,6 +8334,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { settings_fov->set_min(MIN_FOV); settings_fov->set_step(0.1); settings_fov->set_value(EDITOR_GET("editors/3d/default_fov")); + settings_fov->set_select_all_on_focus(true); settings_vbc->add_margin_child(TTR("Perspective FOV (deg.):"), settings_fov); settings_znear = memnew(SpinBox); @@ -7594,6 +8342,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { settings_znear->set_min(MIN_Z); settings_znear->set_step(0.01); settings_znear->set_value(EDITOR_GET("editors/3d/default_z_near")); + settings_znear->set_select_all_on_focus(true); settings_vbc->add_margin_child(TTR("View Z-Near:"), settings_znear); settings_zfar = memnew(SpinBox); @@ -7601,10 +8350,11 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { settings_zfar->set_min(MIN_Z); settings_zfar->set_step(0.1); settings_zfar->set_value(EDITOR_GET("editors/3d/default_z_far")); + settings_zfar->set_select_all_on_focus(true); settings_vbc->add_margin_child(TTR("View Z-Far:"), settings_zfar); for (uint32_t i = 0; i < VIEWPORTS_COUNT; ++i) { - settings_dialog->connect("confirmed", callable_mp(viewports[i], &Node3DEditorViewport::_view_settings_confirmed), varray(0.0)); + settings_dialog->connect("confirmed", callable_mp(viewports[i], &Node3DEditorViewport::_view_settings_confirmed).bind(0.0)); } /* XFORM DIALOG */ @@ -7626,6 +8376,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { for (int i = 0; i < 3; i++) { xform_translate[i] = memnew(LineEdit); xform_translate[i]->set_h_size_flags(SIZE_EXPAND_FILL); + xform_translate[i]->set_select_all_on_focus(true); xform_hbc->add_child(xform_translate[i]); } @@ -7639,6 +8390,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { for (int i = 0; i < 3; i++) { xform_rotate[i] = memnew(LineEdit); xform_rotate[i]->set_h_size_flags(SIZE_EXPAND_FILL); + xform_rotate[i]->set_select_all_on_focus(true); xform_hbc->add_child(xform_rotate[i]); } @@ -7652,6 +8404,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { for (int i = 0; i < 3; i++) { xform_scale[i] = memnew(LineEdit); xform_scale[i]->set_h_size_flags(SIZE_EXPAND_FILL); + xform_scale[i]->set_select_all_on_focus(true); xform_hbc->add_child(xform_scale[i]); } @@ -7669,7 +8422,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { selected = nullptr; - set_process_unhandled_key_input(true); + set_process_shortcut_input(true); add_to_group("_spatial_editor_group"); EDITOR_DEF("editors/3d/manipulator_gizmo_size", 80); @@ -7677,8 +8430,10 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { EDITOR_DEF("editors/3d/manipulator_gizmo_opacity", 0.9); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::FLOAT, "editors/3d/manipulator_gizmo_opacity", PROPERTY_HINT_RANGE, "0,1,0.01")); EDITOR_DEF("editors/3d/navigation/show_viewport_rotation_gizmo", true); + EDITOR_DEF("editors/3d/navigation/show_viewport_navigation_gizmo", DisplayServer::get_singleton()->is_touchscreen_available()); current_hover_gizmo_handle = -1; + current_hover_gizmo_handle_secondary = false; { //sun popup @@ -7702,7 +8457,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { CenterContainer *sun_direction_center = memnew(CenterContainer); sun_direction = memnew(Control); - sun_direction->set_custom_minimum_size(Size2i(128, 128) * EDSCALE); + sun_direction->set_custom_minimum_size(Size2(128, 128) * EDSCALE); sun_direction_center->add_child(sun_direction); sun_vb->add_margin_child(TTR("Sun Direction"), sun_direction_center); sun_direction->connect("gui_input", callable_mp(this, &Node3DEditor::_sun_direction_input)); @@ -7728,8 +8483,8 @@ void fragment() { )"); sun_direction_material.instantiate(); sun_direction_material->set_shader(sun_direction_shader); - sun_direction_material->set_shader_param("sun_direction", Vector3(0, 0, 1)); - sun_direction_material->set_shader_param("sun_color", Vector3(1, 1, 1)); + sun_direction_material->set_shader_parameter("sun_direction", Vector3(0, 0, 1)); + sun_direction_material->set_shader_parameter("sun_color", Vector3(1, 1, 1)); sun_direction->set_material(sun_direction_material); HBoxContainer *sun_angle_hbox = memnew(HBoxContainer); @@ -7765,6 +8520,7 @@ void fragment() { sun_color->set_edit_alpha(false); sun_vb->add_margin_child(TTR("Sun Color"), sun_color); sun_color->connect("color_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); + sun_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(sun_color->get_picker())); sun_energy = memnew(EditorSpinSlider); sun_vb->add_margin_child(TTR("Sun Energy"), sun_energy); @@ -7779,8 +8535,8 @@ void fragment() { sun_add_to_scene = memnew(Button); sun_add_to_scene->set_text(TTR("Add Sun to Scene")); - sun_add_to_scene->set_tooltip(TTR("Adds a DirectionalLight3D node matching the preview sun settings to the current scene.\nHold Shift while clicking to also add the preview environment to the current scene.")); - sun_add_to_scene->connect("pressed", callable_mp(this, &Node3DEditor::_add_sun_to_scene), varray(false)); + sun_add_to_scene->set_tooltip_text(TTR("Adds a DirectionalLight3D node matching the preview sun settings to the current scene.\nHold Shift while clicking to also add the preview environment to the current scene.")); + sun_add_to_scene->connect("pressed", callable_mp(this, &Node3DEditor::_add_sun_to_scene).bind(false)); sun_vb->add_spacer(); sun_vb->add_child(sun_add_to_scene); @@ -7810,10 +8566,12 @@ void fragment() { environ_sky_color = memnew(ColorPickerButton); environ_sky_color->set_edit_alpha(false); environ_sky_color->connect("color_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); + environ_sky_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(environ_sky_color->get_picker())); environ_vb->add_margin_child(TTR("Sky Color"), environ_sky_color); environ_ground_color = memnew(ColorPickerButton); environ_ground_color->connect("color_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); environ_ground_color->set_edit_alpha(false); + environ_ground_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(environ_ground_color->get_picker())); environ_vb->add_margin_child(TTR("Ground Color"), environ_ground_color); environ_energy = memnew(EditorSpinSlider); environ_energy->connect("value_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); @@ -7825,29 +8583,29 @@ void fragment() { environ_ao_button = memnew(Button); environ_ao_button->set_text(TTR("AO")); environ_ao_button->set_toggle_mode(true); - environ_ao_button->connect("pressed", callable_mp(this, &Node3DEditor::_preview_settings_changed), varray(), CONNECT_DEFERRED); + environ_ao_button->connect("pressed", callable_mp(this, &Node3DEditor::_preview_settings_changed), CONNECT_DEFERRED); fx_vb->add_child(environ_ao_button); environ_glow_button = memnew(Button); environ_glow_button->set_text(TTR("Glow")); environ_glow_button->set_toggle_mode(true); - environ_glow_button->connect("pressed", callable_mp(this, &Node3DEditor::_preview_settings_changed), varray(), CONNECT_DEFERRED); + environ_glow_button->connect("pressed", callable_mp(this, &Node3DEditor::_preview_settings_changed), CONNECT_DEFERRED); fx_vb->add_child(environ_glow_button); environ_tonemap_button = memnew(Button); environ_tonemap_button->set_text(TTR("Tonemap")); environ_tonemap_button->set_toggle_mode(true); - environ_tonemap_button->connect("pressed", callable_mp(this, &Node3DEditor::_preview_settings_changed), varray(), CONNECT_DEFERRED); + environ_tonemap_button->connect("pressed", callable_mp(this, &Node3DEditor::_preview_settings_changed), CONNECT_DEFERRED); fx_vb->add_child(environ_tonemap_button); environ_gi_button = memnew(Button); environ_gi_button->set_text(TTR("GI")); environ_gi_button->set_toggle_mode(true); - environ_gi_button->connect("pressed", callable_mp(this, &Node3DEditor::_preview_settings_changed), varray(), CONNECT_DEFERRED); + environ_gi_button->connect("pressed", callable_mp(this, &Node3DEditor::_preview_settings_changed), CONNECT_DEFERRED); fx_vb->add_child(environ_gi_button); environ_vb->add_margin_child(TTR("Post Process"), fx_vb); environ_add_to_scene = memnew(Button); environ_add_to_scene->set_text(TTR("Add Environment to Scene")); - environ_add_to_scene->set_tooltip(TTR("Adds a WorldEnvironment node matching the preview environment settings to the current scene.\nHold Shift while clicking to also add the preview sun to the current scene.")); - environ_add_to_scene->connect("pressed", callable_mp(this, &Node3DEditor::_add_environment_to_scene), varray(false)); + environ_add_to_scene->set_tooltip_text(TTR("Adds a WorldEnvironment node matching the preview environment settings to the current scene.\nHold Shift while clicking to also add the preview sun to the current scene.")); + environ_add_to_scene->connect("pressed", callable_mp(this, &Node3DEditor::_add_environment_to_scene).bind(false)); environ_vb->add_spacer(); environ_vb->add_child(environ_add_to_scene); @@ -7863,6 +8621,10 @@ void fragment() { preview_environment = memnew(WorldEnvironment); environment.instantiate(); preview_environment->set_environment(environment); + if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { + camera_attributes.instantiate(); + preview_environment->set_camera_attributes(camera_attributes); + } Ref<Sky> sky; sky.instantiate(); sky_material.instantiate(); @@ -7874,19 +8636,26 @@ void fragment() { _preview_settings_changed(); } } - Node3DEditor::~Node3DEditor() { memdelete(preview_node); + if (preview_sun_dangling && preview_sun) { + memdelete(preview_sun); + } + if (preview_env_dangling && preview_environment) { + memdelete(preview_environment); + } } void Node3DEditorPlugin::make_visible(bool p_visible) { if (p_visible) { spatial_editor->show(); spatial_editor->set_process(true); + spatial_editor->set_physics_process(true); } else { spatial_editor->hide(); spatial_editor->set_process(false); + spatial_editor->set_physics_process(false); } } @@ -7988,14 +8757,12 @@ void Node3DEditor::remove_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) { _update_gizmos_menu(); } -Node3DEditorPlugin::Node3DEditorPlugin(EditorNode *p_node) { - editor = p_node; - spatial_editor = memnew(Node3DEditor(p_node)); +Node3DEditorPlugin::Node3DEditorPlugin() { + spatial_editor = memnew(Node3DEditor); spatial_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); - editor->get_main_control()->add_child(spatial_editor); + EditorNode::get_singleton()->get_main_screen_control()->add_child(spatial_editor); spatial_editor->hide(); - spatial_editor->connect("transform_key_request", Callable(editor->get_inspector_dock(), "_transform_keyed")); } Node3DEditorPlugin::~Node3DEditorPlugin() { diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index eba3989f9d..a1fd9757d0 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -1,54 +1,63 @@ -/*************************************************************************/ -/* node_3d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* node_3d_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 NODE_3D_EDITOR_PLUGIN_H #define NODE_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/editor_scale.h" #include "editor/plugins/node_3d_editor_gizmos.h" -#include "scene/3d/camera_3d.h" -#include "scene/3d/light_3d.h" -#include "scene/3d/visual_instance_3d.h" -#include "scene/3d/world_environment.h" -#include "scene/gui/panel_container.h" -#include "scene/resources/environment.h" -#include "scene/resources/fog_material.h" -#include "scene/resources/sky_material.h" - +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/spin_box.h" + +class AcceptDialog; +class CheckBox; +class ColorPickerButton; +class ConfirmationDialog; +class DirectionalLight3D; +class EditorData; +class EditorSpinSlider; +class HSplitContainer; +class LineEdit; +class MenuButton; class Node3DEditor; class Node3DEditorViewport; +class OptionButton; +class PanelContainer; +class ProceduralSkyMaterial; +class SubViewport; class SubViewportContainer; -class DirectionalLight3D; +class VSplitContainer; class WorldEnvironment; +class ViewportNavigationControl; class ViewportRotationControl : public Control { GDCLASS(ViewportRotationControl, Control); @@ -69,7 +78,7 @@ class ViewportRotationControl : public Control { Vector<Color> axis_colors; Vector<int> axis_menu_options; Vector2i orbiting_mouse_start; - bool orbiting = false; + int orbiting_index = -1; int focused_axis = -2; const float AXIS_CIRCLE_RADIUS = 8.0f * EDSCALE; @@ -82,6 +91,8 @@ protected: void _get_sorted_axis(Vector<Axis2D> &r_axis); void _update_focus(); void _on_mouse_exited(); + void _process_click(int p_index, Vector2 p_position, bool p_pressed); + void _process_drag(Ref<InputEventWithModifiers> p_event, int p_index, Vector2 p_position, Vector2 p_relative_position); public: void set_viewport(Node3DEditorViewport *p_viewport); @@ -90,6 +101,7 @@ public: class Node3DEditorViewport : public Control { GDCLASS(Node3DEditorViewport, Control); friend class Node3DEditor; + friend class ViewportNavigationControl; friend class ViewportRotationControl; enum { VIEW_TOP, @@ -105,6 +117,7 @@ class Node3DEditorViewport : public Control { VIEW_PERSPECTIVE, VIEW_ENVIRONMENT, VIEW_ORTHOGONAL, + VIEW_SWITCH_PERSPECTIVE_ORTHOGONAL, VIEW_HALF_RESOLUTION, VIEW_AUDIO_LISTENER, VIEW_AUDIO_DOPPLER, @@ -137,6 +150,7 @@ class Node3DEditorViewport : public Control { VIEW_DISPLAY_DEBUG_CLUSTER_DECALS, VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES, VIEW_DISPLAY_DEBUG_OCCLUDERS, + VIEW_DISPLAY_MOTION_VECTORS, VIEW_LOCK_ROTATION, VIEW_CINEMATIC_PREVIEW, @@ -186,29 +200,29 @@ private: ViewType view_type; void _menu_option(int p_option); void _set_auto_orthogonal(); - Node3D *preview_node; - AABB *preview_bounds; + Node3D *preview_node = nullptr; + bool update_preview_node = false; + Point2 preview_node_viewport_pos; + Vector3 preview_node_pos; + AABB *preview_bounds = nullptr; Vector<String> selected_files; - AcceptDialog *accept; + AcceptDialog *accept = nullptr; - Node *target_node; + Node *target_node = nullptr; Point2 drop_pos; - EditorNode *editor; - EditorData *editor_data; - EditorSelection *editor_selection; - UndoRedo *undo_redo; + EditorSelection *editor_selection = nullptr; - CheckBox *preview_camera; - SubViewportContainer *subviewport_container; + CheckBox *preview_camera = nullptr; + SubViewportContainer *subviewport_container = nullptr; - MenuButton *view_menu; - PopupMenu *display_submenu; + MenuButton *view_menu = nullptr; + PopupMenu *display_submenu = nullptr; - Control *surface; - SubViewport *viewport; - Camera3D *camera; - bool transforming; + Control *surface = nullptr; + SubViewport *viewport = nullptr; + Camera3D *camera = nullptr; + bool transforming = false; bool orthogonal; bool auto_orthogonal; bool lock_rotation; @@ -218,17 +232,23 @@ private: real_t freelook_speed; Vector2 previous_mouse_position; - Label *info_label; - Label *cinema_label; - Label *locked_label; - Label *zoom_limit_label; + Label *info_label = nullptr; + Label *cinema_label = nullptr; + Label *locked_label = nullptr; + Label *zoom_limit_label = nullptr; + + Label *preview_material_label = nullptr; + Label *preview_material_label_desc = nullptr; - VBoxContainer *top_right_vbox; - ViewportRotationControl *rotation_control; - Gradient *frame_time_gradient; - Label *cpu_time_label; - Label *gpu_time_label; - Label *fps_label; + VBoxContainer *top_right_vbox = nullptr; + VBoxContainer *bottom_center_vbox = nullptr; + ViewportNavigationControl *position_control = nullptr; + ViewportNavigationControl *look_control = nullptr; + ViewportRotationControl *rotation_control = nullptr; + Gradient *frame_time_gradient = nullptr; + Label *cpu_time_label = nullptr; + Label *gpu_time_label = nullptr; + Label *fps_label = nullptr; struct _RayResult { Node3D *item = nullptr; @@ -240,13 +260,15 @@ private: void _compute_edit(const Point2 &p_point); void _clear_selected(); void _select_clicked(bool p_allow_locked); - ObjectID _select_ray(const Point2 &p_pos); + ObjectID _select_ray(const Point2 &p_pos) const; void _find_items_at_pos(const Point2 &p_pos, Vector<_RayResult> &r_results, bool p_include_locked); Vector3 _get_ray_pos(const Vector2 &p_pos) const; Vector3 _get_ray(const Vector2 &p_pos) const; Point2 _point_to_screen(const Vector3 &p_point); Transform3D _get_camera_transform() const; int get_selected_count() const; + void cancel_transform(); + void _update_shrink(); Vector3 _get_camera_position() const; Vector3 _get_camera_normal() const; @@ -266,10 +288,12 @@ private: float get_fov() const; ObjectID clicked; + ObjectID material_target; Vector<_RayResult> selection_results; - bool clicked_wants_append; + bool clicked_wants_append = false; + bool selection_in_progress = false; - PopupMenu *selection_menu; + PopupMenu *selection_menu = nullptr; enum NavigationZoomStyle { NAVIGATION_ZOOM_VERTICAL, @@ -281,7 +305,8 @@ private: NAVIGATION_PAN, NAVIGATION_ZOOM, NAVIGATION_ORBIT, - NAVIGATION_LOOK + NAVIGATION_LOOK, + NAVIGATION_MOVE }; enum TransformMode { TRANSFORM_NONE, @@ -310,9 +335,13 @@ private: Point2 mouse_pos; Point2 original_mouse_pos; bool snap = false; + bool show_rotation_line = false; Ref<EditorNode3DGizmo> gizmo; int gizmo_handle = 0; + bool gizmo_handle_secondary = false; Variant gizmo_initial_value; + bool original_local; + bool instant; } _edit; struct Cursor { @@ -346,7 +375,7 @@ private: real_t zoom_indicator_delay; int zoom_failed_attempts_count = 0; - RID move_gizmo_instance[3], move_plane_gizmo_instance[3], rotate_gizmo_instance[4], scale_gizmo_instance[3], scale_plane_gizmo_instance[3]; + RID move_gizmo_instance[3], move_plane_gizmo_instance[3], rotate_gizmo_instance[4], scale_gizmo_instance[3], scale_plane_gizmo_instance[3], axis_gizmo_instance[3]; String last_message; String message; @@ -356,6 +385,7 @@ private: void _view_settings_confirmed(real_t p_interp_delta); void _update_camera(real_t p_interp_delta); + void _update_navigation_controls_visibility(); Transform3D to_camera_transform(const Cursor &p_cursor) const; void _draw(); @@ -366,11 +396,12 @@ private: void _sinput(const Ref<InputEvent> &p_event); void _update_freelook(real_t delta); - Node3DEditor *spatial_editor; + Node3DEditor *spatial_editor = nullptr; - Camera3D *previewing; - Camera3D *preview; + Camera3D *previewing = nullptr; + Camera3D *preview = nullptr; + bool previewing_camera; bool previewing_cinema; bool _is_node_locked(const Node *p_node); void _preview_exited_scene(); @@ -388,25 +419,36 @@ private: Node *_sanitize_preview_node(Node *p_node) const; - void _create_preview(const Vector<String> &files) const; - void _remove_preview(); + void _create_preview_node(const Vector<String> &files) const; + void _remove_preview_node(); + bool _apply_preview_material(ObjectID p_target, const Point2 &p_point) const; + void _reset_preview_material() const; + void _remove_preview_material(); bool _cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node); bool _create_instance(Node *parent, String &path, const Point2 &p_point); void _perform_drop_data(); - bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); void _project_settings_changed(); - Transform3D _compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local); + Transform3D _compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local, bool p_orthogonal); + + void begin_transform(TransformMode p_mode, bool instant); + void commit_transform(); + void update_transform(Point2 p_mousepos, bool p_shift); + void finish_transform(); + + void register_shortcut_action(const String &p_path, const String &p_name, Key p_keycode); + void shortcut_changed_callback(const Ref<Shortcut> p_shortcut, const String &p_shortcut_path); protected: void _notification(int p_what); static void _bind_methods(); public: - void update_surface() { surface->update(); } + void update_surface() { surface->queue_redraw(); } void update_transform_gizmo_view(); void set_can_preview(Camera3D *p_preview); @@ -425,7 +467,7 @@ public: SubViewport *get_viewport_node() { return viewport; } Camera3D *get_camera_3d() { return camera; } // return the default camera object. - Node3DEditorViewport(Node3DEditor *p_spatial_editor, EditorNode *p_editor, int p_index); + Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p_index); ~Node3DEditorViewport(); }; @@ -438,13 +480,13 @@ public: Transform3D original_local; Transform3D last_xform; // last transform bool last_xform_dirty; - Node3D *sp; + Node3D *sp = nullptr; RID sbox_instance; RID sbox_instance_offset; RID sbox_instance_xray; RID sbox_instance_xray_offset; Ref<EditorNode3DGizmo> gizmo; - Map<int, Transform3D> subgizmos; // map ID -> initial transform + HashMap<int, Transform3D> subgizmos; // map ID -> initial transform Node3DEditorSelectedItem() { sp = nullptr; @@ -520,13 +562,13 @@ public: }; private: - EditorNode *editor; - EditorSelection *editor_selection; + EditorSelection *editor_selection = nullptr; - Node3DEditorViewportContainer *viewport_base; + Node3DEditorViewportContainer *viewport_base = nullptr; Node3DEditorViewport *viewports[VIEWPORTS_COUNT]; - VSplitContainer *shader_split; - HSplitContainer *palette_split; + VSplitContainer *shader_split = nullptr; + HSplitContainer *left_panel_split = nullptr; + HSplitContainer *right_panel_split = nullptr; ///// @@ -534,17 +576,17 @@ private: RID origin; RID origin_instance; - bool origin_enabled; + bool origin_enabled = false; RID grid[3]; RID grid_instance[3]; bool grid_visible[3]; //currently visible bool grid_enable[3]; //should be always visible if true - bool grid_enabled; + bool grid_enabled = false; bool grid_init_draw = false; - Camera3D::Projection grid_camera_last_update_perspective = Camera3D::PROJECTION_PERSPECTIVE; - Vector3 grid_camera_last_update_position = Vector3(); + Camera3D::ProjectionType grid_camera_last_update_perspective = Camera3D::PROJECTION_PERSPECTIVE; + Vector3 grid_camera_last_update_position; - Ref<ArrayMesh> move_gizmo[3], move_plane_gizmo[3], rotate_gizmo[4], scale_gizmo[3], scale_plane_gizmo[3]; + Ref<ArrayMesh> move_gizmo[3], move_plane_gizmo[3], rotate_gizmo[4], scale_gizmo[3], scale_plane_gizmo[3], axis_gizmo[3]; Ref<StandardMaterial3D> gizmo_color[3]; Ref<StandardMaterial3D> plane_gizmo_color[3]; Ref<ShaderMaterial> rotate_gizmo_color[3]; @@ -554,6 +596,7 @@ private: Ref<Node3DGizmo> current_hover_gizmo; int current_hover_gizmo_handle; + bool current_hover_gizmo_handle_secondary; real_t snap_translate_value; real_t snap_rotate_value; @@ -570,9 +613,14 @@ private: Ref<StandardMaterial3D> cursor_material; // Scene drag and drop support - Node3D *preview_node; + Node3D *preview_node = nullptr; AABB preview_bounds; + Ref<Material> preview_material; + Ref<Material> preview_reset_material; + ObjectID preview_material_target; + int preview_material_surface = -1; + struct Gizmo { bool visible = false; real_t scale = 0; @@ -610,31 +658,31 @@ private: Button *tool_button[TOOL_MAX]; Button *tool_option_button[TOOL_OPT_MAX]; - MenuButton *transform_menu; - PopupMenu *gizmos_menu; - MenuButton *view_menu; + MenuButton *transform_menu = nullptr; + PopupMenu *gizmos_menu = nullptr; + MenuButton *view_menu = nullptr; - AcceptDialog *accept; + AcceptDialog *accept = nullptr; - ConfirmationDialog *snap_dialog; - ConfirmationDialog *xform_dialog; - ConfirmationDialog *settings_dialog; + ConfirmationDialog *snap_dialog = nullptr; + ConfirmationDialog *xform_dialog = nullptr; + ConfirmationDialog *settings_dialog = nullptr; bool snap_enabled; bool snap_key_enabled; - LineEdit *snap_translate; - LineEdit *snap_rotate; - LineEdit *snap_scale; + LineEdit *snap_translate = nullptr; + LineEdit *snap_rotate = nullptr; + LineEdit *snap_scale = nullptr; LineEdit *xform_translate[3]; LineEdit *xform_rotate[3]; LineEdit *xform_scale[3]; - OptionButton *xform_type; + OptionButton *xform_type = nullptr; - VBoxContainer *settings_vbc; - SpinBox *settings_fov; - SpinBox *settings_znear; - SpinBox *settings_zfar; + VBoxContainer *settings_vbc = nullptr; + SpinBox *settings_fov = nullptr; + SpinBox *settings_znear = nullptr; + SpinBox *settings_zfar = nullptr; void _snap_changed(); void _snap_update(); @@ -644,19 +692,16 @@ private: void _menu_gizmo_toggled(int p_option); void _update_camera_override_button(bool p_game_running); void _update_camera_override_viewport(Object *p_viewport); - HBoxContainer *hbc_menu; // Used for secondary menu items which are displayed depending on the currently selected node // (such as MeshInstance's "Mesh" menu). - PanelContainer *context_menu_container; - HBoxContainer *hbc_context_menu; + PanelContainer *context_menu_panel = nullptr; + HBoxContainer *context_menu_hbox = nullptr; void _generate_selection_boxes(); - UndoRedo *undo_redo; int camera_override_viewport_id; void _init_indicators(); - void _update_context_menu_stylebox(); void _update_gizmos_menu(); void _update_gizmos_menu_theme(); void _init_grid(); @@ -665,15 +710,16 @@ private: void _toggle_maximize_view(Object *p_viewport); - Node *custom_camera; + Node *custom_camera = nullptr; Object *_get_editor_data(Object *p_what); Ref<Environment> viewport_environment; - Node3D *selected; + Node3D *selected = nullptr; void _request_gizmo(Object *p_obj); + void _request_gizmo_for_id(ObjectID p_id); void _set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform = Transform3D()); void _clear_subgizmo_selection(Object *p_obj = nullptr); @@ -689,23 +735,26 @@ private: void _selection_changed(); void _refresh_menu_icons(); + bool do_snap_selected_nodes_to_floor = false; + void _snap_selected_nodes_to_floor(); + // Preview Sun and Environment uint32_t world_env_count = 0; uint32_t directional_light_count = 0; - Button *sun_button; - Label *sun_state; - Label *sun_title; - VBoxContainer *sun_vb; - Popup *sun_environ_popup; - Control *sun_direction; - EditorSpinSlider *sun_angle_altitude; - EditorSpinSlider *sun_angle_azimuth; - ColorPickerButton *sun_color; - EditorSpinSlider *sun_energy; - EditorSpinSlider *sun_max_distance; - Button *sun_add_to_scene; + Button *sun_button = nullptr; + Label *sun_state = nullptr; + Label *sun_title = nullptr; + VBoxContainer *sun_vb = nullptr; + Popup *sun_environ_popup = nullptr; + Control *sun_direction = nullptr; + EditorSpinSlider *sun_angle_altitude = nullptr; + EditorSpinSlider *sun_angle_azimuth = nullptr; + ColorPickerButton *sun_color = nullptr; + EditorSpinSlider *sun_energy = nullptr; + EditorSpinSlider *sun_max_distance = nullptr; + Button *sun_add_to_scene = nullptr; void _sun_direction_draw(); void _sun_direction_input(const Ref<InputEvent> &p_event); @@ -716,24 +765,27 @@ private: Ref<Shader> sun_direction_shader; Ref<ShaderMaterial> sun_direction_material; - Button *environ_button; - Label *environ_state; - Label *environ_title; - VBoxContainer *environ_vb; - ColorPickerButton *environ_sky_color; - ColorPickerButton *environ_ground_color; - EditorSpinSlider *environ_energy; - Button *environ_ao_button; - Button *environ_glow_button; - Button *environ_tonemap_button; - Button *environ_gi_button; - Button *environ_add_to_scene; - - Button *sun_environ_settings; - - DirectionalLight3D *preview_sun; - WorldEnvironment *preview_environment; + Button *environ_button = nullptr; + Label *environ_state = nullptr; + Label *environ_title = nullptr; + VBoxContainer *environ_vb = nullptr; + ColorPickerButton *environ_sky_color = nullptr; + ColorPickerButton *environ_ground_color = nullptr; + EditorSpinSlider *environ_energy = nullptr; + Button *environ_ao_button = nullptr; + Button *environ_glow_button = nullptr; + Button *environ_tonemap_button = nullptr; + Button *environ_gi_button = nullptr; + Button *environ_add_to_scene = nullptr; + + Button *sun_environ_settings = nullptr; + + DirectionalLight3D *preview_sun = nullptr; + bool preview_sun_dangling = false; + WorldEnvironment *preview_environment = nullptr; + bool preview_env_dangling = false; Ref<Environment> environment; + Ref<CameraAttributesPractical> camera_attributes; Ref<ProceduralSkyMaterial> sky_material; bool sun_environ_updating = false; @@ -752,7 +804,7 @@ private: protected: void _notification(int p_what); //void _gui_input(InputEvent p_event); - virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; + virtual void shortcut_input(const Ref<InputEvent> &p_event) override; static void _bind_methods(); @@ -770,12 +822,14 @@ public: ToolMode get_tool_mode() const { return tool_mode; } bool are_local_coords_enabled() const { return tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->is_pressed(); } + void set_local_coords_enabled(bool on) const { tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_pressed(on); } bool is_snap_enabled() const { return snap_enabled ^ snap_key_enabled; } double get_translate_snap() const; double get_rotate_snap() const; double get_scale_snap() const; Ref<ArrayMesh> get_move_gizmo(int idx) const { return move_gizmo[idx]; } + Ref<ArrayMesh> get_axis_gizmo(int idx) const { return axis_gizmo[idx]; } Ref<ArrayMesh> get_move_plane_gizmo(int idx) const { return move_plane_gizmo[idx]; } Ref<ArrayMesh> get_rotate_gizmo(int idx) const { return rotate_gizmo[idx]; } Ref<ArrayMesh> get_scale_gizmo(int idx) const { return scale_gizmo[idx]; } @@ -788,19 +842,24 @@ public: void select_gizmo_highlight_axis(int p_axis); void set_custom_camera(Node *p_camera) { custom_camera = p_camera; } - void set_undo_redo(UndoRedo *p_undo_redo) { undo_redo = p_undo_redo; } Dictionary get_state() const; void set_state(const Dictionary &p_state); Ref<Environment> get_viewport_environment() { return viewport_environment; } - UndoRedo *get_undo_redo() { return undo_redo; } - void add_control_to_menu_panel(Control *p_control); void remove_control_from_menu_panel(Control *p_control); + void add_control_to_left_panel(Control *p_control); + void remove_control_from_left_panel(Control *p_control); + + void add_control_to_right_panel(Control *p_control); + void remove_control_from_right_panel(Control *p_control); + + void move_control_to_left_panel(Control *p_control); + void move_control_to_right_panel(Control *p_control); + VSplitContainer *get_shader_split(); - HSplitContainer *get_palette_split(); Node3D *get_single_selected_node() { return selected; } bool is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo); @@ -810,11 +869,27 @@ public: Ref<EditorNode3DGizmo> get_current_hover_gizmo() const { return current_hover_gizmo; } void set_current_hover_gizmo(Ref<EditorNode3DGizmo> p_gizmo) { current_hover_gizmo = p_gizmo; } - void set_current_hover_gizmo_handle(int p_id) { current_hover_gizmo_handle = p_id; } - int get_current_hover_gizmo_handle() const { return current_hover_gizmo_handle; } + void set_current_hover_gizmo_handle(int p_id, bool p_secondary) { + current_hover_gizmo_handle = p_id; + current_hover_gizmo_handle_secondary = p_secondary; + } + + int get_current_hover_gizmo_handle(bool &r_secondary) const { + r_secondary = current_hover_gizmo_handle_secondary; + return current_hover_gizmo_handle; + } void set_can_preview(Camera3D *p_preview); + void set_preview_material(Ref<Material> p_material) { preview_material = p_material; } + Ref<Material> get_preview_material() { return preview_material; } + void set_preview_reset_material(Ref<Material> p_material) { preview_reset_material = p_material; } + Ref<Material> get_preview_reset_material() const { return preview_reset_material; } + void set_preview_material_target(ObjectID p_object_id) { preview_material_target = p_object_id; } + ObjectID get_preview_material_target() const { return preview_material_target; } + void set_preview_material_surface(int p_surface) { preview_material_surface = p_surface; } + int get_preview_material_surface() const { return preview_material_surface; } + Node3DEditorViewport *get_editor_viewport(int p_idx) { ERR_FAIL_INDEX_V(p_idx, static_cast<int>(VIEWPORTS_COUNT), nullptr); return viewports[p_idx]; @@ -826,15 +901,14 @@ public: void edit(Node3D *p_spatial); void clear(); - Node3DEditor(EditorNode *p_editor); + Node3DEditor(); ~Node3DEditor(); }; class Node3DEditorPlugin : public EditorPlugin { GDCLASS(Node3DEditorPlugin, EditorPlugin); - Node3DEditor *spatial_editor; - EditorNode *editor; + Node3DEditor *spatial_editor = nullptr; public: Node3DEditor *get_spatial_editor() { return spatial_editor; } @@ -850,8 +924,35 @@ public: virtual void edited_scene_changed() override; - Node3DEditorPlugin(EditorNode *p_node); + Node3DEditorPlugin(); ~Node3DEditorPlugin(); }; +class ViewportNavigationControl : public Control { + GDCLASS(ViewportNavigationControl, Control); + + Node3DEditorViewport *viewport = nullptr; + Vector2i focused_mouse_start; + Vector2 focused_pos; + bool hovered = false; + int focused_index = -1; + Node3DEditorViewport::NavigationMode nav_mode = Node3DEditorViewport::NavigationMode::NAVIGATION_NONE; + + const float AXIS_CIRCLE_RADIUS = 30.0f * EDSCALE; + +protected: + void _notification(int p_what); + virtual void gui_input(const Ref<InputEvent> &p_event) override; + void _draw(); + void _on_mouse_entered(); + void _on_mouse_exited(); + void _process_click(int p_index, Vector2 p_position, bool p_pressed); + void _process_drag(int p_index, Vector2 p_position, Vector2 p_relative_position); + void _update_navigation(); + +public: + void set_navigation_mode(Node3DEditorViewport::NavigationMode p_nav_mode); + void set_viewport(Node3DEditorViewport *p_viewport); +}; + #endif // NODE_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.cpp b/editor/plugins/occluder_instance_3d_editor_plugin.cpp index 2dd760275e..cfe95f1cfa 100644 --- a/editor/plugins/occluder_instance_3d_editor_plugin.cpp +++ b/editor/plugins/occluder_instance_3d_editor_plugin.cpp @@ -1,42 +1,45 @@ -/*************************************************************************/ -/* occluder_instance_3d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* occluder_instance_3d_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 "occluder_instance_3d_editor_plugin.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" + void OccluderInstance3DEditorPlugin::_bake_select_file(const String &p_file) { if (occluder_instance) { OccluderInstance3D::BakeError err; if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == occluder_instance) { - err = occluder_instance->bake(occluder_instance, p_file); + err = occluder_instance->bake_scene(occluder_instance, p_file); } else { - err = occluder_instance->bake(occluder_instance->get_parent(), p_file); + err = occluder_instance->bake_scene(occluder_instance->get_parent(), p_file); } switch (err) { @@ -59,6 +62,10 @@ void OccluderInstance3DEditorPlugin::_bake_select_file(const String &p_file) { EditorNode::get_singleton()->show_warning(TTR("No meshes to bake.\nMake sure there is at least one MeshInstance3D node in the scene whose visual layers are part of the OccluderInstance3D's Bake Mask property.")); break; } + case OccluderInstance3D::BAKE_ERROR_CANT_SAVE: { + EditorNode::get_singleton()->show_warning(TTR("Could not save the new occluder at the specified path:") + " " + p_file); + break; + } default: { } } @@ -94,11 +101,10 @@ void OccluderInstance3DEditorPlugin::_bind_methods() { ClassDB::bind_method("_bake", &OccluderInstance3DEditorPlugin::_bake); } -OccluderInstance3DEditorPlugin::OccluderInstance3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +OccluderInstance3DEditorPlugin::OccluderInstance3DEditorPlugin() { bake = memnew(Button); bake->set_flat(true); - bake->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); + bake->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); bake->set_text(TTR("Bake Occluders")); bake->hide(); bake->connect("pressed", Callable(this, "_bake")); @@ -107,7 +113,7 @@ OccluderInstance3DEditorPlugin::OccluderInstance3DEditorPlugin(EditorNode *p_nod file_dialog = memnew(EditorFileDialog); file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); - file_dialog->add_filter("*.occ ; Occluder3D"); + file_dialog->add_filter("*.occ", "Occluder3D"); file_dialog->set_title(TTR("Select occluder bake file:")); file_dialog->connect("file_selected", callable_mp(this, &OccluderInstance3DEditorPlugin::_bake_select_file)); bake->add_child(file_dialog); diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.h b/editor/plugins/occluder_instance_3d_editor_plugin.h index a9aa0b74e3..54ac95c9e1 100644 --- a/editor/plugins/occluder_instance_3d_editor_plugin.h +++ b/editor/plugins/occluder_instance_3d_editor_plugin.h @@ -1,50 +1,50 @@ -/*************************************************************************/ -/* occluder_instance_3d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* occluder_instance_3d_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 OCCLUDER_INSTANCE_3D_EDITOR_PLUGIN_H #define OCCLUDER_INSTANCE_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/occluder_instance_3d.h" #include "scene/resources/material.h" +class EditorFileDialog; + class OccluderInstance3DEditorPlugin : public EditorPlugin { GDCLASS(OccluderInstance3DEditorPlugin, EditorPlugin); - OccluderInstance3D *occluder_instance; + OccluderInstance3D *occluder_instance = nullptr; - Button *bake; - EditorNode *editor; + Button *bake = nullptr; - EditorFileDialog *file_dialog; + EditorFileDialog *file_dialog = nullptr; void _bake_select_file(const String &p_file); void _bake(); @@ -59,8 +59,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - OccluderInstance3DEditorPlugin(EditorNode *p_node); + OccluderInstance3DEditorPlugin(); ~OccluderInstance3DEditorPlugin(); }; -#endif +#endif // OCCLUDER_INSTANCE_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/ot_features_plugin.cpp b/editor/plugins/ot_features_plugin.cpp deleted file mode 100644 index d2daa4fa8d..0000000000 --- a/editor/plugins/ot_features_plugin.cpp +++ /dev/null @@ -1,208 +0,0 @@ -/*************************************************************************/ -/* ot_features_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "ot_features_plugin.h" - -#include "editor/editor_scale.h" - -void OpenTypeFeaturesEditor::_value_changed(double val) { - if (setting) { - return; - } - - emit_changed(get_edited_property(), spin->get_value()); -} - -void OpenTypeFeaturesEditor::update_property() { - double val = get_edited_object()->get(get_edited_property()); - setting = true; - spin->set_value(val); - setting = false; -} - -void OpenTypeFeaturesEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - - button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - button->set_size(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))->get_size()); - spin->set_custom_label_color(true, base); - } -} - -void OpenTypeFeaturesEditor::_remove_feature() { - get_edited_object()->set(get_edited_property(), -1); -} - -void OpenTypeFeaturesEditor::_bind_methods() { -} - -OpenTypeFeaturesEditor::OpenTypeFeaturesEditor() { - HBoxContainer *bc = memnew(HBoxContainer); - add_child(bc); - - spin = memnew(EditorSpinSlider); - spin->set_flat(true); - bc->add_child(spin); - add_focusable(spin); - spin->connect("value_changed", callable_mp(this, &OpenTypeFeaturesEditor::_value_changed)); - spin->set_h_size_flags(SIZE_EXPAND_FILL); - - spin->set_min(0); - spin->set_max(65536); - spin->set_step(1); - spin->set_hide_slider(false); - spin->set_allow_greater(false); - spin->set_allow_lesser(false); - - button = memnew(Button); - button->set_tooltip(RTR("Remove feature")); - button->set_flat(true); - bc->add_child(button); - - button->connect("pressed", callable_mp(this, &OpenTypeFeaturesEditor::_remove_feature)); - - setting = false; -} - -/*************************************************************************/ - -void OpenTypeFeaturesAdd::_add_feature(int p_option) { - get_edited_object()->set("opentype_features/" + TS->tag_to_name(p_option), 1); -} - -void OpenTypeFeaturesAdd::update_property() { - menu->clear(); - menu_ss->clear(); - menu_cv->clear(); - menu_cu->clear(); - bool have_ss = false; - bool have_cv = false; - bool have_cu = false; - Dictionary features = Object::cast_to<Control>(get_edited_object())->get_theme_font(SNAME("font"))->get_feature_list(); - for (const Variant *ftr = features.next(nullptr); ftr != nullptr; ftr = features.next(ftr)) { - String ftr_name = TS->tag_to_name(*ftr); - if (ftr_name.begins_with("stylistic_set_")) { - menu_ss->add_item(ftr_name.capitalize(), (int32_t)*ftr); - have_ss = true; - } else if (ftr_name.begins_with("character_variant_")) { - menu_cv->add_item(ftr_name.capitalize(), (int32_t)*ftr); - have_cv = true; - } else if (ftr_name.begins_with("custom_")) { - menu_cu->add_item(ftr_name.replace("custom_", ""), (int32_t)*ftr); - have_cu = true; - } else { - menu->add_item(ftr_name.capitalize(), (int32_t)*ftr); - } - } - if (have_ss) { - menu->add_submenu_item(RTR("Stylistic Sets"), "SSMenu"); - } - if (have_cv) { - menu->add_submenu_item(RTR("Character Variants"), "CVMenu"); - } - if (have_cu) { - menu->add_submenu_item(RTR("Custom"), "CUMenu"); - } -} - -void OpenTypeFeaturesAdd::_features_menu() { - Size2 size = get_size(); - menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); - menu->reset_size(); - menu->popup(); -} - -void OpenTypeFeaturesAdd::_notification(int p_what) { - if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { - set_label(""); - button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - button->set_size(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))->get_size()); - } -} - -void OpenTypeFeaturesAdd::_bind_methods() { -} - -OpenTypeFeaturesAdd::OpenTypeFeaturesAdd() { - menu = memnew(PopupMenu); - add_child(menu); - - menu_cv = memnew(PopupMenu); - menu_cv->set_name("CVMenu"); - menu->add_child(menu_cv); - - menu_ss = memnew(PopupMenu); - menu_ss->set_name("SSMenu"); - menu->add_child(menu_ss); - - menu_cu = memnew(PopupMenu); - menu_cu->set_name("CUMenu"); - menu->add_child(menu_cu); - - button = memnew(Button); - button->set_flat(true); - button->set_text(RTR("Add feature...")); - button->set_tooltip(RTR("Add feature...")); - add_child(button); - - button->connect("pressed", callable_mp(this, &OpenTypeFeaturesAdd::_features_menu)); - menu->connect("id_pressed", callable_mp(this, &OpenTypeFeaturesAdd::_add_feature)); - menu_cv->connect("id_pressed", callable_mp(this, &OpenTypeFeaturesAdd::_add_feature)); - menu_ss->connect("id_pressed", callable_mp(this, &OpenTypeFeaturesAdd::_add_feature)); - menu_cu->connect("id_pressed", callable_mp(this, &OpenTypeFeaturesAdd::_add_feature)); -} - -/*************************************************************************/ - -bool EditorInspectorPluginOpenTypeFeatures::can_handle(Object *p_object) { - return (Object::cast_to<Control>(p_object) != nullptr); -} - -bool EditorInspectorPluginOpenTypeFeatures::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { - if (p_path == "opentype_features/_new") { - OpenTypeFeaturesAdd *editor = memnew(OpenTypeFeaturesAdd); - add_property_editor(p_path, editor); - return true; - } else if (p_path.begins_with("opentype_features")) { - OpenTypeFeaturesEditor *editor = memnew(OpenTypeFeaturesEditor); - add_property_editor(p_path, editor); - return true; - } - return false; -} - -/*************************************************************************/ - -OpenTypeFeaturesEditorPlugin::OpenTypeFeaturesEditorPlugin(EditorNode *p_node) { - Ref<EditorInspectorPluginOpenTypeFeatures> ftr_plugin; - ftr_plugin.instantiate(); - EditorInspector::add_inspector_plugin(ftr_plugin); -} diff --git a/editor/plugins/ot_features_plugin.h b/editor/plugins/ot_features_plugin.h deleted file mode 100644 index 073fe53a52..0000000000 --- a/editor/plugins/ot_features_plugin.h +++ /dev/null @@ -1,103 +0,0 @@ -/*************************************************************************/ -/* ot_features_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 OT_FEATURES_PLUGIN_H -#define OT_FEATURES_PLUGIN_H - -#include "editor/editor_node.h" -#include "editor/editor_plugin.h" -#include "editor/editor_properties.h" - -/*************************************************************************/ - -class OpenTypeFeaturesEditor : public EditorProperty { - GDCLASS(OpenTypeFeaturesEditor, EditorProperty); - EditorSpinSlider *spin; - bool setting = true; - void _value_changed(double p_val); - Button *button = nullptr; - - void _remove_feature(); - -protected: - void _notification(int p_what); - static void _bind_methods(); - -public: - virtual void update_property() override; - OpenTypeFeaturesEditor(); -}; - -/*************************************************************************/ - -class OpenTypeFeaturesAdd : public EditorProperty { - GDCLASS(OpenTypeFeaturesAdd, EditorProperty); - - Button *button = nullptr; - PopupMenu *menu = nullptr; - PopupMenu *menu_ss = nullptr; - PopupMenu *menu_cv = nullptr; - PopupMenu *menu_cu = nullptr; - - void _add_feature(int p_option); - void _features_menu(); - -protected: - void _notification(int p_what); - static void _bind_methods(); - -public: - virtual void update_property() override; - - OpenTypeFeaturesAdd(); -}; - -/*************************************************************************/ - -class EditorInspectorPluginOpenTypeFeatures : public EditorInspectorPlugin { - GDCLASS(EditorInspectorPluginOpenTypeFeatures, EditorInspectorPlugin); - -public: - virtual bool can_handle(Object *p_object) override; - virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; -}; - -/*************************************************************************/ - -class OpenTypeFeaturesEditorPlugin : public EditorPlugin { - GDCLASS(OpenTypeFeaturesEditorPlugin, EditorPlugin); - -public: - OpenTypeFeaturesEditorPlugin(EditorNode *p_node); - - virtual String get_name() const override { return "OpenTypeFeatures"; } -}; - -#endif // OT_FEATURES_PLUGIN_H diff --git a/editor/plugins/packed_scene_editor_plugin.cpp b/editor/plugins/packed_scene_editor_plugin.cpp new file mode 100644 index 0000000000..3239375e1e --- /dev/null +++ b/editor/plugins/packed_scene_editor_plugin.cpp @@ -0,0 +1,81 @@ +/**************************************************************************/ +/* packed_scene_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 "packed_scene_editor_plugin.h" + +#include "editor/editor_node.h" +#include "scene/gui/button.h" +#include "scene/resources/packed_scene.h" +#include "scene/scene_string_names.h" + +void PackedSceneEditor::_on_open_scene_pressed() { + // Using deferred call because changing scene updates the Inspector and thus destroys this plugin. + callable_mp(EditorNode::get_singleton(), &EditorNode::open_request).call_deferred(packed_scene->get_path()); +} + +void PackedSceneEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + open_scene_button->set_icon(get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons"))); + } break; + } +} + +PackedSceneEditor::PackedSceneEditor(Ref<PackedScene> &p_packed_scene) { + packed_scene = p_packed_scene; + + open_scene_button = EditorInspector::create_inspector_action_button(TTR("Open Scene")); + open_scene_button->connect(SNAME("pressed"), callable_mp(this, &PackedSceneEditor::_on_open_scene_pressed)); + open_scene_button->set_disabled(!packed_scene->get_path().get_file().is_valid_filename()); + add_child(open_scene_button); + + add_child(memnew(Control)); // Add padding before the regular properties. +} + +/////////////////////// + +bool EditorInspectorPluginPackedScene::can_handle(Object *p_object) { + return Object::cast_to<PackedScene>(p_object) != nullptr; +} + +void EditorInspectorPluginPackedScene::parse_begin(Object *p_object) { + Ref<PackedScene> packed_scene(p_object); + PackedSceneEditor *editor = memnew(PackedSceneEditor(packed_scene)); + add_custom_control(editor); +} + +/////////////////////// + +PackedSceneEditorPlugin::PackedSceneEditorPlugin() { + Ref<EditorInspectorPluginPackedScene> plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} diff --git a/editor/plugins/packed_scene_editor_plugin.h b/editor/plugins/packed_scene_editor_plugin.h new file mode 100644 index 0000000000..308bcf1c05 --- /dev/null +++ b/editor/plugins/packed_scene_editor_plugin.h @@ -0,0 +1,68 @@ +/**************************************************************************/ +/* packed_scene_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 PACKED_SCENE_EDITOR_PLUGIN_H +#define PACKED_SCENE_EDITOR_PLUGIN_H + +#include "editor/editor_inspector.h" +#include "editor/editor_plugin.h" +#include "scene/gui/box_container.h" + +class PackedSceneEditor : public VBoxContainer { + GDCLASS(PackedSceneEditor, VBoxContainer); + + Ref<PackedScene> packed_scene; + Button *open_scene_button; + + void _on_open_scene_pressed(); + +protected: + void _notification(int p_what); + +public: + PackedSceneEditor(Ref<PackedScene> &p_packed_scene); +}; + +class EditorInspectorPluginPackedScene : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginPackedScene, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; +}; + +class PackedSceneEditorPlugin : public EditorPlugin { + GDCLASS(PackedSceneEditorPlugin, EditorPlugin); + +public: + PackedSceneEditorPlugin(); +}; + +#endif // PACKED_SCENE_EDITOR_PLUGIN_H diff --git a/editor/plugins/packed_scene_translation_parser_plugin.cpp b/editor/plugins/packed_scene_translation_parser_plugin.cpp index b492c27f41..83d7778196 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.cpp +++ b/editor/plugins/packed_scene_translation_parser_plugin.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* packed_scene_translation_parser_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* packed_scene_translation_parser_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 "packed_scene_translation_parser_plugin.h" @@ -43,7 +43,7 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, // These properties are translated with the tr() function in the C++ code when being set or updated. Error err; - RES loaded_res = ResourceLoader::load(p_path, "PackedScene", ResourceFormatLoader::CACHE_MODE_REUSE, &err); + Ref<Resource> loaded_res = ResourceLoader::load(p_path, "PackedScene", ResourceFormatLoader::CACHE_MODE_REUSE, &err); if (err) { ERR_PRINT("Failed to load " + p_path); return err; @@ -123,7 +123,7 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, PackedSceneEditorTranslationParserPlugin::PackedSceneEditorTranslationParserPlugin() { // Scene Node's properties containing strings that will be fetched for translation. lookup_properties.insert("text"); - lookup_properties.insert("hint_tooltip"); + lookup_properties.insert("tooltip_text"); lookup_properties.insert("placeholder_text"); lookup_properties.insert("items"); lookup_properties.insert("title"); @@ -132,10 +132,7 @@ PackedSceneEditorTranslationParserPlugin::PackedSceneEditorTranslationParserPlug lookup_properties.insert("script"); // Exception list (to prevent false positives). - exception_list.insert("LineEdit", Vector<StringName>()); - exception_list["LineEdit"].append("text"); - exception_list.insert("TextEdit", Vector<StringName>()); - exception_list["TextEdit"].append("text"); - exception_list.insert("CodeEdit", Vector<StringName>()); - exception_list["CodeEdit"].append("text"); + exception_list.insert("LineEdit", { "text" }); + exception_list.insert("TextEdit", { "text" }); + exception_list.insert("CodeEdit", { "text" }); } diff --git a/editor/plugins/packed_scene_translation_parser_plugin.h b/editor/plugins/packed_scene_translation_parser_plugin.h index fc19496eb6..d1a92ded32 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.h +++ b/editor/plugins/packed_scene_translation_parser_plugin.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* packed_scene_translation_parser_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* packed_scene_translation_parser_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 PACKED_SCENE_TRANSLATION_PARSER_PLUGIN_H #define PACKED_SCENE_TRANSLATION_PARSER_PLUGIN_H @@ -37,9 +37,9 @@ class PackedSceneEditorTranslationParserPlugin : public EditorTranslationParserP GDCLASS(PackedSceneEditorTranslationParserPlugin, EditorTranslationParserPlugin); // Scene Node's properties that contain translation strings. - Set<StringName> lookup_properties; + HashSet<String> lookup_properties; // Properties from specific Nodes that should be ignored. - Map<StringName, Vector<StringName>> exception_list; + HashMap<String, Vector<String>> exception_list; public: virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override; diff --git a/editor/plugins/path_2d_editor_plugin.cpp b/editor/plugins/path_2d_editor_plugin.cpp index c50673559c..63f6643ba3 100644 --- a/editor/plugins/path_2d_editor_plugin.cpp +++ b/editor/plugins/path_2d_editor_plugin.cpp @@ -1,51 +1,53 @@ -/*************************************************************************/ -/* path_2d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* path_2d_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 "path_2d_editor_plugin.h" #include "canvas_item_editor_plugin.h" #include "core/io/file_access.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "scene/gui/menu_button.h" void Path2DEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: { - //button_create->set_icon( get_icon("Edit","EditorIcons")); - //button_edit->set_icon( get_icon("MovePoint","EditorIcons")); - //set_pressed_button(button_edit); - //button_edit->set_pressed(true); - - } break; - case NOTIFICATION_PHYSICS_PROCESS: { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + curve_edit->set_icon(get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); + curve_edit_curve->set_icon(get_theme_icon(SNAME("CurveCurve"), SNAME("EditorIcons"))); + curve_create->set_icon(get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); + curve_del->set_icon(get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); + curve_close->set_icon(get_theme_icon(SNAME("CurveClose"), SNAME("EditorIcons"))); } break; } } @@ -118,6 +120,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } // Check for point deletion. + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if ((mb->get_button_index() == MouseButton::RIGHT && mode == MODE_EDIT) || (mb->get_button_index() == MouseButton::LEFT && mode == MODE_DELETE)) { if (dist_to_p < grab_threshold) { undo_redo->create_action(TTR("Remove Point from Curve")); @@ -149,9 +152,10 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } // Check for point creation. - if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && ((mb->is_command_pressed() && mode == MODE_EDIT) || mode == MODE_CREATE)) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && ((mb->is_command_or_control_pressed() && mode == MODE_EDIT) || mode == MODE_CREATE)) { Ref<Curve2D> curve = node->get_curve(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Point to Curve")); undo_redo->add_do_method(curve.ptr(), "add_point", cpoint); undo_redo->add_undo_method(curve.ptr(), "remove_point", curve->get_point_count()); @@ -187,6 +191,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { insertion_point = curve->get_point_count() - 2; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Split Curve")); undo_redo->add_do_method(curve.ptr(), "add_point", xform.affine_inverse().xform(gpoint2), Vector2(0, 0), Vector2(0, 0), insertion_point + 1); undo_redo->add_undo_method(curve.ptr(), "remove_point", insertion_point + 1); @@ -210,6 +215,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && action != ACTION_NONE) { Ref<Curve2D> curve = node->get_curve(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); Vector2 new_pos = moving_from + xform.affine_inverse().basis_xform(gpoint - moving_screen_from); switch (action) { case ACTION_NONE: @@ -485,6 +491,7 @@ void Path2DEditor::_mode_selected(int p_mode) { return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove Point from Curve")); undo_redo->add_do_method(node->get_curve().ptr(), "add_point", begin); undo_redo->add_undo_method(node->get_curve().ptr(), "remove_point", node->get_curve()->get_point_count()); @@ -516,10 +523,8 @@ void Path2DEditor::_handle_option_pressed(int p_option) { } } -Path2DEditor::Path2DEditor(EditorNode *p_editor) { +Path2DEditor::Path2DEditor() { canvas_item_editor = nullptr; - editor = p_editor; - undo_redo = editor->get_undo_redo(); mirror_handle_angle = true; mirror_handle_length = true; on_edge = false; @@ -532,44 +537,44 @@ Path2DEditor::Path2DEditor(EditorNode *p_editor) { sep = memnew(VSeparator); base_hb->add_child(sep); + curve_edit = memnew(Button); curve_edit->set_flat(true); - curve_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); curve_edit->set_toggle_mode(true); curve_edit->set_focus_mode(Control::FOCUS_NONE); - curve_edit->set_tooltip(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD) + TTR("Click: Add Point") + "\n" + TTR("Left Click: Split Segment (in curve)") + "\n" + TTR("Right Click: Delete Point")); - curve_edit->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected), varray(MODE_EDIT)); + curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Left Click: Split Segment (in curve)") + "\n" + TTR("Right Click: Delete Point")); + curve_edit->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT)); base_hb->add_child(curve_edit); + curve_edit_curve = memnew(Button); curve_edit_curve->set_flat(true); - curve_edit_curve->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCurve"), SNAME("EditorIcons"))); curve_edit_curve->set_toggle_mode(true); curve_edit_curve->set_focus_mode(Control::FOCUS_NONE); - curve_edit_curve->set_tooltip(TTR("Select Control Points (Shift+Drag)")); - curve_edit_curve->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected), varray(MODE_EDIT_CURVE)); + curve_edit_curve->set_tooltip_text(TTR("Select Control Points (Shift+Drag)")); + curve_edit_curve->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT_CURVE)); base_hb->add_child(curve_edit_curve); + curve_create = memnew(Button); curve_create->set_flat(true); - curve_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); curve_create->set_toggle_mode(true); curve_create->set_focus_mode(Control::FOCUS_NONE); - curve_create->set_tooltip(TTR("Add Point (in empty space)")); - curve_create->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected), varray(MODE_CREATE)); + curve_create->set_tooltip_text(TTR("Add Point (in empty space)")); + curve_create->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CREATE)); base_hb->add_child(curve_create); + curve_del = memnew(Button); curve_del->set_flat(true); - curve_del->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); curve_del->set_toggle_mode(true); curve_del->set_focus_mode(Control::FOCUS_NONE); - curve_del->set_tooltip(TTR("Delete Point")); - curve_del->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected), varray(MODE_DELETE)); + curve_del->set_tooltip_text(TTR("Delete Point")); + curve_del->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_DELETE)); base_hb->add_child(curve_del); + curve_close = memnew(Button); curve_close->set_flat(true); - curve_close->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveClose"), SNAME("EditorIcons"))); curve_close->set_focus_mode(Control::FOCUS_NONE); - curve_close->set_tooltip(TTR("Close Curve")); - curve_close->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected), varray(ACTION_CLOSE)); + curve_close->set_tooltip_text(TTR("Close Curve")); + curve_close->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(ACTION_CLOSE)); base_hb->add_child(curve_close); PopupMenu *menu; @@ -610,9 +615,8 @@ void Path2DEditorPlugin::make_visible(bool p_visible) { } } -Path2DEditorPlugin::Path2DEditorPlugin(EditorNode *p_node) { - editor = p_node; - path2d_editor = memnew(Path2DEditor(p_node)); +Path2DEditorPlugin::Path2DEditorPlugin() { + path2d_editor = memnew(Path2DEditor); CanvasItemEditor::get_singleton()->add_control_to_menu_panel(path2d_editor); path2d_editor->hide(); } diff --git a/editor/plugins/path_2d_editor_plugin.h b/editor/plugins/path_2d_editor_plugin.h index 210a5a140d..25af0bf616 100644 --- a/editor/plugins/path_2d_editor_plugin.h +++ b/editor/plugins/path_2d_editor_plugin.h @@ -1,54 +1,53 @@ -/*************************************************************************/ -/* path_2d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* path_2d_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 PATH_2D_EDITOR_PLUGIN_H #define PATH_2D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/2d/path_2d.h" +#include "scene/gui/box_container.h" +#include "scene/gui/separator.h" class CanvasItemEditor; +class MenuButton; class Path2DEditor : public HBoxContainer { GDCLASS(Path2DEditor, HBoxContainer); - UndoRedo *undo_redo; + CanvasItemEditor *canvas_item_editor = nullptr; + Panel *panel = nullptr; + Path2D *node = nullptr; - CanvasItemEditor *canvas_item_editor; - EditorNode *editor; - Panel *panel; - Path2D *node; - - HBoxContainer *base_hb; - Separator *sep; + HBoxContainer *base_hb = nullptr; + Separator *sep = nullptr; enum Mode { MODE_CREATE, @@ -59,12 +58,12 @@ class Path2DEditor : public HBoxContainer { }; Mode mode; - Button *curve_create; - Button *curve_edit; - Button *curve_edit_curve; - Button *curve_del; - Button *curve_close; - MenuButton *handle_menu; + Button *curve_create = nullptr; + Button *curve_edit = nullptr; + Button *curve_edit_curve = nullptr; + Button *curve_del = nullptr; + Button *curve_close = nullptr; + MenuButton *handle_menu = nullptr; bool mirror_handle_angle; bool mirror_handle_length; @@ -83,11 +82,11 @@ class Path2DEditor : public HBoxContainer { }; Action action; - int action_point; + int action_point = 0; Point2 moving_from; Point2 moving_screen_from; - float orig_in_length; - float orig_out_length; + float orig_in_length = 0.0f; + float orig_out_length = 0.0f; Vector2 edge_point; void _mode_selected(int p_mode); @@ -105,14 +104,13 @@ public: bool forward_gui_input(const Ref<InputEvent> &p_event); void forward_canvas_draw_over_viewport(Control *p_overlay); void edit(Node *p_path2d); - Path2DEditor(EditorNode *p_editor); + Path2DEditor(); }; class Path2DEditorPlugin : public EditorPlugin { GDCLASS(Path2DEditorPlugin, EditorPlugin); - Path2DEditor *path2d_editor; - EditorNode *editor; + Path2DEditor *path2d_editor = nullptr; public: virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return path2d_editor->forward_gui_input(p_event); } @@ -124,7 +122,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - Path2DEditorPlugin(EditorNode *p_node); + Path2DEditorPlugin(); ~Path2DEditorPlugin(); }; diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index c31b893498..75cd04bee8 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -1,57 +1,72 @@ -/*************************************************************************/ -/* path_3d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* path_3d_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 "path_3d_editor_plugin.h" #include "core/math/geometry_2d.h" #include "core/math/geometry_3d.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "node_3d_editor_plugin.h" +#include "scene/gui/menu_button.h" #include "scene/resources/curve.h" -String Path3DGizmo::get_handle_name(int p_id) const { +static bool _is_in_handle(int p_id, int p_num_points) { + int t = (p_id + 1) % 2; + int idx = (p_id + 1) / 2; + // order of points is [out_0, out_1, in_1, out_2, in_2, ... out_n-1, in_n-1, in_n] + if (idx == 0) { + return false; + } else if (idx == (p_num_points - 1)) { + return true; + } else { + return (t == 1); + } +} + +String Path3DGizmo::get_handle_name(int p_id, bool p_secondary) const { Ref<Curve3D> c = path->get_curve(); if (c.is_null()) { return ""; } - if (p_id < c->get_point_count()) { + if (!p_secondary) { return TTR("Curve Point #") + itos(p_id); } - p_id = p_id - c->get_point_count() + 1; - - int idx = p_id / 2; - int t = p_id % 2; + // (p_id + 1) Accounts for the first point only having an "out" handle + int idx = (p_id + 1) / 2; String n = TTR("Curve Point #") + itos(idx); - if (t == 0) { + if (_is_in_handle(p_id, c->get_point_count())) { n += " In"; } else { n += " Out"; @@ -60,24 +75,22 @@ String Path3DGizmo::get_handle_name(int p_id) const { return n; } -Variant Path3DGizmo::get_handle_value(int p_id) const { +Variant Path3DGizmo::get_handle_value(int p_id, bool p_secondary) const { Ref<Curve3D> c = path->get_curve(); if (c.is_null()) { return Variant(); } - if (p_id < c->get_point_count()) { + if (!p_secondary) { original = c->get_point_position(p_id); return original; } - p_id = p_id - c->get_point_count() + 1; - - int idx = p_id / 2; - int t = p_id % 2; + // (p_id + 1) Accounts for the first point only having an "out" handle + int idx = (p_id + 1) / 2; Vector3 ofs; - if (t == 0) { + if (_is_in_handle(p_id, c->get_point_count())) { ofs = c->get_point_in(idx); } else { ofs = c->get_point_out(idx); @@ -88,7 +101,7 @@ Variant Path3DGizmo::get_handle_value(int p_id) const { return ofs; } -void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) { +void Path3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { Ref<Curve3D> c = path->get_curve(); if (c.is_null()) { return; @@ -100,8 +113,8 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point Vector3 ray_dir = p_camera->project_ray_normal(p_point); // Setting curve point positions - if (p_id < c->get_point_count()) { - const Plane p = Plane(p_camera->get_transform().basis.get_axis(2), gt.xform(original)); + if (!p_secondary) { + const Plane p = Plane(p_camera->get_transform().basis.get_column(2), gt.xform(original)); Vector3 inters; @@ -118,14 +131,12 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point return; } - p_id = p_id - c->get_point_count() + 1; - - int idx = p_id / 2; - int t = p_id % 2; + // (p_id + 1) Accounts for the first point only having an "out" handle + int idx = (p_id + 1) / 2; Vector3 base = c->get_point_position(idx); - Plane p(p_camera->get_transform().basis.get_axis(2), gt.xform(original)); + Plane p(p_camera->get_transform().basis.get_column(2), gt.xform(original)); Vector3 inters; @@ -143,7 +154,7 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point local.snap(Vector3(snap, snap, snap)); } - if (t == 0) { + if (_is_in_handle(p_id, c->get_point_count())) { c->set_point_in(idx, local); if (Path3DEditorPlugin::singleton->mirror_angle_enabled()) { c->set_point_out(idx, Path3DEditorPlugin::singleton->mirror_length_enabled() ? -local : (-local.normalized() * orig_out_length)); @@ -157,15 +168,15 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point } } -void Path3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cancel) { +void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { Ref<Curve3D> c = path->get_curve(); if (c.is_null()) { return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - if (p_id < c->get_point_count()) { + if (!p_secondary) { if (p_cancel) { c->set_point_position(p_id, p_restore); return; @@ -178,12 +189,10 @@ void Path3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cance return; } - p_id = p_id - c->get_point_count() + 1; - - int idx = p_id / 2; - int t = p_id % 2; + // (p_id + 1) Accounts for the first point only having an "out" handle + int idx = (p_id + 1) / 2; - if (t == 0) { + if (_is_in_handle(p_id, c->get_point_count())) { if (p_cancel) { c->set_point_in(p_id, p_restore); return; @@ -231,70 +240,92 @@ void Path3DGizmo::redraw() { return; } - Vector<Vector3> v3a = c->tessellate(); - //Vector<Vector3> v3a=c->get_baked_points(); + real_t interval = 0.1; + const real_t length = c->get_baked_length(); - int v3s = v3a.size(); - if (v3s == 0) { - return; - } - Vector<Vector3> v3p; - const Vector3 *r = v3a.ptr(); - - // BUG: the following won't work when v3s, avoid drawing as a temporary workaround. - for (int i = 0; i < v3s - 1; i++) { - v3p.push_back(r[i]); - v3p.push_back(r[i + 1]); - //v3p.push_back(r[i]); - //v3p.push_back(r[i]+Vector3(0,0.2,0)); - } + // 1. Draw curve and bones. + if (length > CMP_EPSILON) { + const int sample_count = int(length / interval) + 2; + interval = length / (sample_count - 1); // Recalculate real interval length. + + Vector<Transform3D> frames; + frames.resize(sample_count); + + { + Transform3D *w = frames.ptrw(); + + for (int i = 0; i < sample_count; i++) { + w[i] = c->sample_baked_with_rotation(i * interval, true, true); + } + } + + const Transform3D *r = frames.ptr(); + Vector<Vector3> v3p; + for (int i = 0; i < sample_count - 1; i++) { + const Vector3 p1 = r[i].origin; + const Vector3 p2 = r[i + 1].origin; + const Vector3 side = r[i].basis.get_column(0); + const Vector3 up = r[i].basis.get_column(1); + const Vector3 forward = r[i].basis.get_column(2); + + // Curve segment. + v3p.push_back(p1); + v3p.push_back(p2); + + // Fish Bone. + v3p.push_back(p1); + v3p.push_back(p1 + (side - forward + up * 0.3) * 0.06); + + v3p.push_back(p1); + v3p.push_back(p1 + (-side - forward + up * 0.3) * 0.06); + } - if (v3p.size() > 1) { add_lines(v3p, path_material); add_collision_segments(v3p); } + // 2. Draw handles. if (Path3DEditorPlugin::singleton->get_edited_path() == path) { - v3p.clear(); - Vector<Vector3> handles; - Vector<Vector3> sec_handles; + Vector<Vector3> v3p; + Vector<Vector3> handle_points; + Vector<Vector3> sec_handle_points; for (int i = 0; i < c->get_point_count(); i++) { Vector3 p = c->get_point_position(i); - handles.push_back(p); - if (i > 0) { - v3p.push_back(p); - v3p.push_back(p + c->get_point_in(i)); - sec_handles.push_back(p + c->get_point_in(i)); - } - + handle_points.push_back(p); + // Push out points first so they get selected if the In and Out points are on top of each other. if (i < c->get_point_count() - 1) { v3p.push_back(p); v3p.push_back(p + c->get_point_out(i)); - sec_handles.push_back(p + c->get_point_out(i)); + sec_handle_points.push_back(p + c->get_point_out(i)); + } + if (i > 0) { + v3p.push_back(p); + v3p.push_back(p + c->get_point_in(i)); + sec_handle_points.push_back(p + c->get_point_in(i)); } } if (v3p.size() > 1) { add_lines(v3p, path_thin_material); } - if (handles.size()) { - add_handles(handles, handles_material); + if (handle_points.size()) { + add_handles(handle_points, handles_material); } - if (sec_handles.size()) { - add_handles(sec_handles, sec_handles_material, Vector<int>(), false, true); + if (sec_handle_points.size()) { + add_handles(sec_handle_points, sec_handles_material, Vector<int>(), false, true); } } } Path3DGizmo::Path3DGizmo(Path3D *p_path) { path = p_path; - set_spatial_node(p_path); + set_node_3d(p_path); orig_in_length = 0; orig_out_length = 0; } -EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!path) { return EditorPlugin::AFTER_GUI_INPUT_PASS; } @@ -319,14 +350,14 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->is_ctrl_pressed()))) { //click into curve, break it down Vector<Vector3> v3a = c->tessellate(); - int idx = 0; int rc = v3a.size(); int closest_seg = -1; Vector3 closest_seg_point; - float closest_d = 1e20; if (rc >= 2) { + int idx = 0; const Vector3 *r = v3a.ptr(); + float closest_d = 1e20; if (p_camera->unproject_position(gt.xform(c->get_point_position(0))).distance_to(mbpos) < click_dist) { return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing @@ -378,7 +409,7 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera } } - UndoRedo *ur = editor->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); if (closest_seg != -1) { //subdivide @@ -395,7 +426,7 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera } else { origin = gt.xform(c->get_point_position(c->get_point_count() - 1)); } - Plane p(p_camera->get_transform().basis.get_axis(2), origin); + Plane p(p_camera->get_transform().basis.get_column(2), origin); Vector3 ray_from = p_camera->project_ray_origin(mbpos); Vector3 ray_dir = p_camera->project_ray_normal(mbpos); @@ -420,21 +451,21 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera // Find the offset and point index of the place to break up. // Also check for the control points. if (dist_to_p < click_dist) { - UndoRedo *ur = editor->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Remove Path Point")); ur->add_do_method(c.ptr(), "remove_point", i); ur->add_undo_method(c.ptr(), "add_point", c->get_point_position(i), c->get_point_in(i), c->get_point_out(i), i); ur->commit_action(); return EditorPlugin::AFTER_GUI_INPUT_STOP; } else if (dist_to_p_out < click_dist) { - UndoRedo *ur = editor->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Remove Out-Control Point")); ur->add_do_method(c.ptr(), "set_point_out", i, Vector3()); ur->add_undo_method(c.ptr(), "set_point_out", i, c->get_point_out(i)); ur->commit_action(); return EditorPlugin::AFTER_GUI_INPUT_STOP; } else if (dist_to_p_in < click_dist) { - UndoRedo *ur = editor->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Remove In-Control Point")); ur->add_do_method(c.ptr(), "set_point_in", i, Vector3()); ur->add_undo_method(c.ptr(), "set_point_in", i, c->get_point_in(i)); @@ -513,7 +544,7 @@ void Path3DEditorPlugin::_close_curve() { if (c->get_point_position(0) == c->get_point_position(c->get_point_count() - 1)) { return; } - UndoRedo *ur = editor->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Close Curve")); ur->add_do_method(c.ptr(), "add_point", c->get_point_position(0), c->get_point_in(0), c->get_point_out(0), -1); ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count()); @@ -539,12 +570,29 @@ void Path3DEditorPlugin::_handle_option_pressed(int p_option) { } } +void Path3DEditorPlugin::_update_theme() { + // TODO: Split the EditorPlugin instance from the UI instance and connect this properly. + // See the 2D path editor for inspiration. + curve_edit->set_icon(Node3DEditor::get_singleton()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); + curve_create->set_icon(Node3DEditor::get_singleton()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); + curve_del->set_icon(Node3DEditor::get_singleton()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); + curve_close->set_icon(Node3DEditor::get_singleton()->get_theme_icon(SNAME("CurveClose"), SNAME("EditorIcons"))); +} + void Path3DEditorPlugin::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - curve_create->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed), make_binds(0)); - curve_edit->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed), make_binds(1)); - curve_del->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed), make_binds(2)); - curve_close->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_close_curve)); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + curve_create->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(0)); + curve_edit->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(1)); + curve_del->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(2)); + curve_close->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_close_curve)); + + _update_theme(); + } break; + + case NOTIFICATION_READY: { + Node3DEditor::get_singleton()->connect("theme_changed", callable_mp(this, &Path3DEditorPlugin::_update_theme)); + } break; } } @@ -553,9 +601,8 @@ void Path3DEditorPlugin::_bind_methods() { Path3DEditorPlugin *Path3DEditorPlugin::singleton = nullptr; -Path3DEditorPlugin::Path3DEditorPlugin(EditorNode *p_node) { +Path3DEditorPlugin::Path3DEditorPlugin() { path = nullptr; - editor = p_node; singleton = this; mirror_handle_angle = true; mirror_handle_length = true; @@ -567,36 +614,36 @@ Path3DEditorPlugin::Path3DEditorPlugin(EditorNode *p_node) { sep = memnew(VSeparator); sep->hide(); Node3DEditor::get_singleton()->add_control_to_menu_panel(sep); + curve_edit = memnew(Button); curve_edit->set_flat(true); - curve_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); curve_edit->set_toggle_mode(true); curve_edit->hide(); curve_edit->set_focus_mode(Control::FOCUS_NONE); - curve_edit->set_tooltip(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD) + TTR("Click: Add Point") + "\n" + TTR("Right Click: Delete Point")); + curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Right Click: Delete Point")); Node3DEditor::get_singleton()->add_control_to_menu_panel(curve_edit); + curve_create = memnew(Button); curve_create->set_flat(true); - curve_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); curve_create->set_toggle_mode(true); curve_create->hide(); curve_create->set_focus_mode(Control::FOCUS_NONE); - curve_create->set_tooltip(TTR("Add Point (in empty space)") + "\n" + TTR("Split Segment (in curve)")); + curve_create->set_tooltip_text(TTR("Add Point (in empty space)") + "\n" + TTR("Split Segment (in curve)")); Node3DEditor::get_singleton()->add_control_to_menu_panel(curve_create); + curve_del = memnew(Button); curve_del->set_flat(true); - curve_del->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); curve_del->set_toggle_mode(true); curve_del->hide(); curve_del->set_focus_mode(Control::FOCUS_NONE); - curve_del->set_tooltip(TTR("Delete Point")); + curve_del->set_tooltip_text(TTR("Delete Point")); Node3DEditor::get_singleton()->add_control_to_menu_panel(curve_del); + curve_close = memnew(Button); curve_close->set_flat(true); - curve_close->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveClose"), SNAME("EditorIcons"))); curve_close->hide(); curve_close->set_focus_mode(Control::FOCUS_NONE); - curve_close->set_tooltip(TTR("Close Curve")); + curve_close->set_tooltip_text(TTR("Close Curve")); Node3DEditor::get_singleton()->add_control_to_menu_panel(curve_close); PopupMenu *menu; diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h index a7da2c07e5..110a0be377 100644 --- a/editor/plugins/path_3d_editor_plugin.h +++ b/editor/plugins/path_3d_editor_plugin.h @@ -1,54 +1,57 @@ -/*************************************************************************/ -/* path_3d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 PATH_EDITOR_PLUGIN_H -#define PATH_EDITOR_PLUGIN_H +/**************************************************************************/ +/* path_3d_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 PATH_3D_EDITOR_PLUGIN_H +#define PATH_3D_EDITOR_PLUGIN_H #include "editor/editor_plugin.h" #include "editor/plugins/node_3d_editor_gizmos.h" #include "scene/3d/camera_3d.h" #include "scene/3d/path_3d.h" +#include "scene/gui/separator.h" + +class MenuButton; class Path3DGizmo : public EditorNode3DGizmo { GDCLASS(Path3DGizmo, EditorNode3DGizmo); - Path3D *path; + Path3D *path = nullptr; mutable Vector3 original; mutable float orig_in_length; mutable float orig_out_length; public: - virtual String get_handle_name(int p_idx) const override; - virtual Variant get_handle_value(int p_id) const override; - virtual void set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) override; - virtual void commit_handle(int p_id, const Variant &p_restore, bool p_cancel = false) override; + virtual String get_handle_name(int p_id, bool p_secondary) const override; + virtual Variant get_handle_value(int p_id, bool p_secondary) const override; + virtual void set_handle(int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + virtual void commit_handle(int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; virtual void redraw() override; Path3DGizmo(Path3D *p_path = nullptr); @@ -69,21 +72,21 @@ public: class Path3DEditorPlugin : public EditorPlugin { GDCLASS(Path3DEditorPlugin, EditorPlugin); - Separator *sep; - Button *curve_create; - Button *curve_edit; - Button *curve_del; - Button *curve_close; - MenuButton *handle_menu; + Separator *sep = nullptr; + Button *curve_create = nullptr; + Button *curve_edit = nullptr; + Button *curve_del = nullptr; + Button *curve_close = nullptr; + MenuButton *handle_menu = nullptr; - EditorNode *editor; + Path3D *path = nullptr; - Path3D *path; + void _update_theme(); void _mode_changed(int p_idx); void _close_curve(); void _handle_option_pressed(int p_option); - bool handle_clicked; + bool handle_clicked = false; bool mirror_handle_angle; bool mirror_handle_length; @@ -100,7 +103,7 @@ public: Path3D *get_edited_path() { return path; } static Path3DEditorPlugin *singleton; - virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; + virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; virtual String get_name() const override { return "Path3D"; } bool has_main_screen() const override { return false; } @@ -113,8 +116,8 @@ public: bool is_handle_clicked() { return handle_clicked; } void set_handle_clicked(bool clicked) { handle_clicked = clicked; } - Path3DEditorPlugin(EditorNode *p_node); + Path3DEditorPlugin(); ~Path3DEditorPlugin(); }; -#endif // PATH_EDITOR_PLUGIN_H +#endif // PATH_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/physical_bone_3d_editor_plugin.cpp b/editor/plugins/physical_bone_3d_editor_plugin.cpp index 9d69bbaa0b..b716cee3ff 100644 --- a/editor/plugins/physical_bone_3d_editor_plugin.cpp +++ b/editor/plugins/physical_bone_3d_editor_plugin.cpp @@ -1,37 +1,37 @@ -/*************************************************************************/ -/* physical_bone_3d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* physical_bone_3d_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 "physical_bone_3d_editor_plugin.h" #include "editor/plugins/node_3d_editor_plugin.h" -#include "scene/3d/physics_body_3d.h" +#include "scene/gui/separator.h" void PhysicalBone3DEditor::_bind_methods() { } @@ -47,8 +47,7 @@ void PhysicalBone3DEditor::_set_move_joint() { } } -PhysicalBone3DEditor::PhysicalBone3DEditor(EditorNode *p_editor) : - editor(p_editor) { +PhysicalBone3DEditor::PhysicalBone3DEditor() { spatial_editor_hb = memnew(HBoxContainer); spatial_editor_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); spatial_editor_hb->set_alignment(BoxContainer::ALIGNMENT_BEGIN); @@ -84,9 +83,7 @@ void PhysicalBone3DEditor::show() { spatial_editor_hb->show(); } -PhysicalBone3DEditorPlugin::PhysicalBone3DEditorPlugin(EditorNode *p_editor) : - editor(p_editor), - physical_bone_editor(editor) {} +PhysicalBone3DEditorPlugin::PhysicalBone3DEditorPlugin() {} void PhysicalBone3DEditorPlugin::make_visible(bool p_visible) { if (p_visible) { diff --git a/editor/plugins/physical_bone_3d_editor_plugin.h b/editor/plugins/physical_bone_3d_editor_plugin.h index d30222d7e6..c9799d3faa 100644 --- a/editor/plugins/physical_bone_3d_editor_plugin.h +++ b/editor/plugins/physical_bone_3d_editor_plugin.h @@ -1,44 +1,46 @@ -/*************************************************************************/ -/* physical_bone_3d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* physical_bone_3d_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 PHYSICAL_BONE_PLUGIN_H -#define PHYSICAL_BONE_PLUGIN_H +#ifndef PHYSICAL_BONE_3D_EDITOR_PLUGIN_H +#define PHYSICAL_BONE_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "scene/3d/physics_body_3d.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" class PhysicalBone3DEditor : public Object { GDCLASS(PhysicalBone3DEditor, Object); - EditorNode *editor; - HBoxContainer *spatial_editor_hb; - Button *button_transform_joint; + HBoxContainer *spatial_editor_hb = nullptr; + Button *button_transform_joint = nullptr; PhysicalBone3D *selected = nullptr; @@ -50,7 +52,7 @@ private: void _set_move_joint(); public: - PhysicalBone3DEditor(EditorNode *p_editor); + PhysicalBone3DEditor(); ~PhysicalBone3DEditor() {} void set_selected(PhysicalBone3D *p_pb); @@ -62,7 +64,6 @@ public: class PhysicalBone3DEditorPlugin : public EditorPlugin { GDCLASS(PhysicalBone3DEditorPlugin, EditorPlugin); - EditorNode *editor; PhysicalBone3D *selected = nullptr; PhysicalBone3DEditor physical_bone_editor; @@ -72,7 +73,7 @@ public: virtual void make_visible(bool p_visible) override; virtual void edit(Object *p_node) override; - PhysicalBone3DEditorPlugin(EditorNode *p_editor); + PhysicalBone3DEditorPlugin(); }; -#endif +#endif // PHYSICAL_BONE_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index e272b96778..fb35668310 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -1,43 +1,52 @@ -/*************************************************************************/ -/* polygon_2d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* polygon_2d_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 "polygon_2d_editor_plugin.h" -#include "canvas_item_editor_plugin.h" -#include "core/input/input.h" -#include "core/io/file_access.h" +#include "core/input/input_event.h" #include "core/math/geometry_2d.h" -#include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/plugins/canvas_item_editor_plugin.h" #include "scene/2d/skeleton_2d.h" +#include "scene/gui/check_box.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/scroll_container.h" +#include "scene/gui/separator.h" +#include "scene/gui/slider.h" +#include "scene/gui/spin_box.h" +#include "scene/gui/split_container.h" +#include "scene/gui/texture_rect.h" +#include "scene/gui/view_panner.h" Node2D *Polygon2DEditor::_get_node() const { return node; @@ -63,10 +72,10 @@ int Polygon2DEditor::_get_polygon_count() const { void Polygon2DEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: { - uv_edit_draw->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - bone_scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + uv_panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); } break; + case NOTIFICATION_READY: { button_uv->set_icon(get_theme_icon(SNAME("Uv"), SNAME("EditorIcons"))); @@ -88,7 +97,13 @@ void Polygon2DEditor::_notification(int p_what) { uv_vscroll->set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE); uv_hscroll->set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + uv_edit_draw->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); + bone_scroll->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible()) { uv_edit->hide(); @@ -141,13 +156,14 @@ void Polygon2DEditor::_sync_bones() { Array new_bones = node->call("_get_bones"); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Sync Bones")); undo_redo->add_do_method(node, "_set_bones", new_bones); undo_redo->add_undo_method(node, "_set_bones", prev_bones); undo_redo->add_do_method(this, "_update_bone_list"); undo_redo->add_undo_method(this, "_update_bone_list"); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); } @@ -183,14 +199,14 @@ void Polygon2DEditor::_update_bone_list() { cb->set_pressed(true); } - cb->connect("pressed", callable_mp(this, &Polygon2DEditor::_bone_paint_selected), varray(i)); + cb->connect("pressed", callable_mp(this, &Polygon2DEditor::_bone_paint_selected).bind(i)); } - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); } void Polygon2DEditor::_bone_paint_selected(int p_index) { - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); } void Polygon2DEditor::_uv_edit_mode_select(int p_mode) { @@ -260,7 +276,7 @@ void Polygon2DEditor::_uv_edit_mode_select(int p_mode) { } uv_edit->set_size(uv_edit->get_size()); // Necessary readjustment of the popup window. - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); } void Polygon2DEditor::_uv_edit_popup_hide() { @@ -270,6 +286,7 @@ void Polygon2DEditor::_uv_edit_popup_hide() { } void Polygon2DEditor::_menu_option(int p_option) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); switch (p_option) { case MODE_EDIT_UV: { if (node->get_texture().is_null()) { @@ -284,13 +301,13 @@ void Polygon2DEditor::_menu_option(int p_option) { undo_redo->create_action(TTR("Create UV Map")); undo_redo->add_do_method(node, "set_uv", points); undo_redo->add_undo_method(node, "set_uv", uvs); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); } if (EditorSettings::get_singleton()->has_setting("interface/dialogs/uv_editor_bounds")) { - uv_edit->popup(EditorSettings::get_singleton()->get("interface/dialogs/uv_editor_bounds")); + uv_edit->popup(EDITOR_GET("interface/dialogs/uv_editor_bounds")); } else { uv_edit->popup_centered_ratio(0.85); } @@ -305,8 +322,8 @@ void Polygon2DEditor::_menu_option(int p_option) { undo_redo->create_action(TTR("Create UV Map")); undo_redo->add_do_method(node, "set_uv", points); undo_redo->add_undo_method(node, "set_uv", uvs); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); } break; case UVEDIT_UV_TO_POLYGON: { @@ -319,8 +336,8 @@ void Polygon2DEditor::_menu_option(int p_option) { undo_redo->create_action(TTR("Create Polygon")); undo_redo->add_do_method(node, "set_polygon", uvs); undo_redo->add_undo_method(node, "set_polygon", points); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); } break; case UVEDIT_UV_CLEAR: { @@ -331,8 +348,8 @@ void Polygon2DEditor::_menu_option(int p_option) { undo_redo->create_action(TTR("Create UV Map")); undo_redo->add_do_method(node, "set_uv", Vector<Vector2>()); undo_redo->add_undo_method(node, "set_uv", uvs); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); } break; case UVEDIT_GRID_SETTINGS: { @@ -382,8 +399,9 @@ void Polygon2DEditor::_update_polygon_editing_state() { void Polygon2DEditor::_commit_action() { // Makes that undo/redoing actions made outside of the UV editor still affect its polygon. - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->add_do_method(CanvasItemEditor::get_singleton(), "update_viewport"); undo_redo->add_undo_method(CanvasItemEditor::get_singleton(), "update_viewport"); undo_redo->commit_action(); @@ -397,31 +415,31 @@ void Polygon2DEditor::_set_use_snap(bool p_use) { void Polygon2DEditor::_set_show_grid(bool p_show) { snap_show_grid = p_show; EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "show_grid", p_show); - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); } void Polygon2DEditor::_set_snap_off_x(real_t p_val) { snap_offset.x = p_val; EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_offset", snap_offset); - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); } void Polygon2DEditor::_set_snap_off_y(real_t p_val) { snap_offset.y = p_val; EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_offset", snap_offset); - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); } void Polygon2DEditor::_set_snap_step_x(real_t p_val) { snap_step.x = p_val; EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_step", snap_step); - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); } void Polygon2DEditor::_set_snap_step_y(real_t p_val) { snap_step.y = p_val; EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_step", snap_step); - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); } void Polygon2DEditor::_uv_mode(int p_mode) { @@ -440,12 +458,18 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { return; } + if (uv_panner->gui_input(p_input)) { + accept_event(); + return; + } + Transform2D mtx; - mtx.elements[2] = -uv_draw_ofs; + mtx.columns[2] = -uv_draw_ofs; mtx.scale_basis(Vector2(uv_draw_zoom, uv_draw_zoom)); - Ref<InputEventMouseButton> mb = p_input; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + Ref<InputEventMouseButton> mb = p_input; if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { @@ -462,7 +486,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { uv_move_current = uv_mode; if (uv_move_current == UV_MODE_CREATE) { if (!uv_create) { - points_prev.resize(0); + points_prev.clear(); Vector2 tuv = mtx.affine_inverse().xform(snap_point(mb->get_position())); points_prev.push_back(tuv); uv_create_to = tuv; @@ -481,7 +505,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { node->set_uv(points_prev); node->set_internal_vertex_count(0); - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); } else { Vector2 tuv = mtx.affine_inverse().xform(snap_point(mb->get_position())); @@ -500,8 +524,8 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { undo_redo->add_undo_method(node, "_set_bones", uv_create_bones_prev); undo_redo->add_do_method(this, "_update_polygon_editing_state"); undo_redo->add_undo_method(this, "_update_polygon_editing_state"); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); uv_drag = false; uv_create = false; @@ -552,8 +576,8 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { undo_redo->add_undo_method(node, "set_internal_vertex_count", internal_vertices); undo_redo->add_do_method(this, "_update_polygon_editing_state"); undo_redo->add_undo_method(this, "_update_polygon_editing_state"); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); } @@ -607,17 +631,17 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { undo_redo->add_undo_method(node, "set_internal_vertex_count", internal_vertices); undo_redo->add_do_method(this, "_update_polygon_editing_state"); undo_redo->add_undo_method(this, "_update_polygon_editing_state"); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); } if (uv_move_current == UV_MODE_EDIT_POINT) { - if (mb->is_shift_pressed() && mb->is_command_pressed()) { + if (mb->is_shift_pressed() && mb->is_command_or_control_pressed()) { uv_move_current = UV_MODE_SCALE; } else if (mb->is_shift_pressed()) { uv_move_current = UV_MODE_MOVE; - } else if (mb->is_command_pressed()) { + } else if (mb->is_command_or_control_pressed()) { uv_move_current = UV_MODE_ROTATE; } } @@ -665,13 +689,13 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { undo_redo->create_action(TTR("Add Custom Polygon")); undo_redo->add_do_method(node, "set_polygons", polygons); undo_redo->add_undo_method(node, "set_polygons", node->get_polygons()); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); } polygon_create.clear(); - } else if (polygon_create.find(closest) == -1) { + } else if (!polygon_create.has(closest)) { //add temporarily if not exists polygon_create.push_back(closest); } @@ -706,8 +730,8 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { undo_redo->create_action(TTR("Remove Custom Polygon")); undo_redo->add_do_method(node, "set_polygons", polygons); undo_redo->add_undo_method(node, "set_polygons", node->get_polygons()); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); } } @@ -734,15 +758,15 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { undo_redo->create_action(TTR("Transform UV Map")); undo_redo->add_do_method(node, "set_uv", node->get_uv()); undo_redo->add_undo_method(node, "set_uv", points_prev); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); } else if (uv_edit_mode[1]->is_pressed() && uv_move_current == UV_MODE_EDIT_POINT) { // Edit polygon. undo_redo->create_action(TTR("Transform Polygon")); undo_redo->add_do_method(node, "set_polygon", node->get_polygon()); undo_redo->add_undo_method(node, "set_polygon", points_prev); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); } @@ -753,8 +777,8 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { undo_redo->create_action(TTR("Paint Bone Weights")); undo_redo->add_do_method(node, "set_bone_weights", bone_painting_bone, node->get_bone_weights(bone_painting_bone)); undo_redo->add_undo_method(node, "set_bone_weights", bone_painting_bone, prev_weights); - undo_redo->add_do_method(uv_edit_draw, "update"); - undo_redo->add_undo_method(uv_edit_draw, "update"); + undo_redo->add_do_method(uv_edit_draw, "queue_redraw"); + undo_redo->add_undo_method(uv_edit_draw, "queue_redraw"); undo_redo->commit_action(); bone_painting = false; } @@ -766,24 +790,14 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { node->set_bone_weights(bone_painting_bone, prev_weights); } - uv_edit_draw->update(); - - } else if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) { - uv_zoom->set_value(uv_zoom->get_value() / (1 - (0.1 * mb->get_factor()))); - } else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) { - uv_zoom->set_value(uv_zoom->get_value() * (1 - (0.1 * mb->get_factor()))); + uv_edit_draw->queue_redraw(); } } Ref<InputEventMouseMotion> mm = p_input; if (mm.is_valid()) { - if ((mm->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE || Input::get_singleton()->is_key_pressed(Key::SPACE)) { - Vector2 drag = mm->get_relative(); - uv_hscroll->set_value(uv_hscroll->get_value() - drag.x); - uv_vscroll->set_value(uv_vscroll->get_value() - drag.y); - - } else if (uv_drag) { + if (uv_drag) { Vector2 uv_drag_to = mm->get_position(); uv_drag_to = snap_point(uv_drag_to); // FIXME: Only works correctly with 'UV_MODE_EDIT_POINT', it's imprecise with the rest. Vector2 drag = mtx.affine_inverse().xform(uv_drag_to) - mtx.affine_inverse().xform(uv_drag_from); @@ -902,14 +916,14 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { node->set_bone_weights(bone_painting_bone, painted_weights); } - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); CanvasItemEditor::get_singleton()->update_viewport(); } else if (polygon_create.size()) { uv_create_to = mtx.affine_inverse().xform(mm->get_position()); - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); } else if (uv_mode == UV_MODE_PAINT_WEIGHT || uv_mode == UV_MODE_CLEAR_WEIGHT) { bone_paint_pos = mm->get_position(); - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); } } @@ -925,6 +939,15 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { } } +void Polygon2DEditor::_uv_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) { + uv_hscroll->set_value(uv_hscroll->get_value() - p_scroll_vec.x); + uv_vscroll->set_value(uv_vscroll->get_value() - p_scroll_vec.y); +} + +void Polygon2DEditor::_uv_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) { + uv_zoom->set_value(uv_zoom->get_value() * p_zoom_factor); +} + void Polygon2DEditor::_uv_scroll_changed(real_t) { if (updating_uv_scroll) { return; @@ -933,7 +956,7 @@ void Polygon2DEditor::_uv_scroll_changed(real_t) { uv_draw_ofs.x = uv_hscroll->get_value(); uv_draw_ofs.y = uv_vscroll->get_value(); uv_draw_zoom = uv_zoom->get_value(); - uv_edit_draw->update(); + uv_edit_draw->queue_redraw(); } void Polygon2DEditor::_uv_draw() { @@ -949,7 +972,7 @@ void Polygon2DEditor::_uv_draw() { String warning; Transform2D mtx; - mtx.elements[2] = -uv_draw_ofs; + mtx.columns[2] = -uv_draw_ofs; mtx.scale_basis(Vector2(uv_draw_zoom, uv_draw_zoom)); RS::get_singleton()->canvas_item_add_set_transform(uv_edit_draw->get_canvas_item(), mtx); @@ -1041,7 +1064,7 @@ void Polygon2DEditor::_uv_draw() { for (int i = 0; i < uvs.size(); i++) { int next = uv_draw_max > 0 ? (i + 1) % uv_draw_max : 0; - if (i < uv_draw_max && uv_drag && uv_move_current == UV_MODE_EDIT_POINT && EDITOR_DEF("editors/polygon_editor/show_previous_outline", true)) { + if (i < uv_draw_max && uv_drag && uv_move_current == UV_MODE_EDIT_POINT && EDITOR_GET("editors/polygon_editor/show_previous_outline")) { uv_edit_draw->draw_line(mtx.xform(points_prev[i]), mtx.xform(points_prev[next]), prev_color, Math::round(EDSCALE)); } @@ -1049,7 +1072,7 @@ void Polygon2DEditor::_uv_draw() { if (uv_create && i == uvs.size() - 1) { next_point = uv_create_to; } - if (i < uv_draw_max /*&& polygons.size() == 0 && polygon_create.size() == 0*/) { //if using or creating polygons, do not show outline (will show polygons instead) + if (i < uv_draw_max) { // If using or creating polygons, do not show outline (will show polygons instead). uv_edit_draw->draw_line(mtx.xform(uvs[i]), mtx.xform(next_point), poly_line_color, Math::round(EDSCALE)); } } @@ -1207,9 +1230,7 @@ Vector2 Polygon2DEditor::snap_point(Vector2 p_target) const { return p_target; } -Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : - AbstractPolygon2DEditor(p_editor) { - node = nullptr; +Polygon2DEditor::Polygon2DEditor() { snap_offset = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_offset", Vector2()); snap_step = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_step", Vector2(10, 10)); use_snap = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_enabled", false); @@ -1218,14 +1239,14 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : button_uv = memnew(Button); button_uv->set_flat(true); add_child(button_uv); - button_uv->set_tooltip(TTR("Open Polygon 2D UV editor.")); - button_uv->connect("pressed", callable_mp(this, &Polygon2DEditor::_menu_option), varray(MODE_EDIT_UV)); + button_uv->set_tooltip_text(TTR("Open Polygon 2D UV editor.")); + button_uv->connect("pressed", callable_mp(this, &Polygon2DEditor::_menu_option).bind(MODE_EDIT_UV)); uv_mode = UV_MODE_EDIT_POINT; uv_edit = memnew(AcceptDialog); add_child(uv_edit); uv_edit->set_title(TTR("Polygon 2D UV Editor")); - uv_edit->connect("cancelled", callable_mp(this, &Polygon2DEditor::_uv_edit_popup_hide)); + uv_edit->connect("canceled", callable_mp(this, &Polygon2DEditor::_uv_edit_popup_hide)); VBoxContainer *uv_main_vb = memnew(VBoxContainer); uv_edit->add_child(uv_main_vb); @@ -1257,10 +1278,10 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : uv_edit_mode[2]->set_button_group(uv_edit_group); uv_edit_mode[3]->set_button_group(uv_edit_group); - uv_edit_mode[0]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select), varray(0)); - uv_edit_mode[1]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select), varray(1)); - uv_edit_mode[2]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select), varray(2)); - uv_edit_mode[3]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select), varray(3)); + uv_edit_mode[0]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select).bind(0)); + uv_edit_mode[1]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select).bind(1)); + uv_edit_mode[2]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select).bind(2)); + uv_edit_mode[3]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select).bind(3)); uv_mode_hb->add_child(memnew(VSeparator)); @@ -1270,21 +1291,21 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : uv_button[i]->set_flat(true); uv_button[i]->set_toggle_mode(true); uv_mode_hb->add_child(uv_button[i]); - uv_button[i]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_mode), varray(i)); + uv_button[i]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_mode).bind(i)); uv_button[i]->set_focus_mode(FOCUS_NONE); } - uv_button[UV_MODE_CREATE]->set_tooltip(TTR("Create Polygon")); - uv_button[UV_MODE_CREATE_INTERNAL]->set_tooltip(TTR("Create Internal Vertex")); - uv_button[UV_MODE_REMOVE_INTERNAL]->set_tooltip(TTR("Remove Internal Vertex")); - uv_button[UV_MODE_EDIT_POINT]->set_tooltip(TTR("Move Points") + "\n" + TTR("Ctrl: Rotate") + "\n" + TTR("Shift: Move All") + "\n" + TTR("Shift+Ctrl: Scale")); - uv_button[UV_MODE_MOVE]->set_tooltip(TTR("Move Polygon")); - uv_button[UV_MODE_ROTATE]->set_tooltip(TTR("Rotate Polygon")); - uv_button[UV_MODE_SCALE]->set_tooltip(TTR("Scale Polygon")); - uv_button[UV_MODE_ADD_POLYGON]->set_tooltip(TTR("Create a custom polygon. Enables custom polygon rendering.")); - uv_button[UV_MODE_REMOVE_POLYGON]->set_tooltip(TTR("Remove a custom polygon. If none remain, custom polygon rendering is disabled.")); - uv_button[UV_MODE_PAINT_WEIGHT]->set_tooltip(TTR("Paint weights with specified intensity.")); - uv_button[UV_MODE_CLEAR_WEIGHT]->set_tooltip(TTR("Unpaint weights with specified intensity.")); + uv_button[UV_MODE_CREATE]->set_tooltip_text(TTR("Create Polygon")); + uv_button[UV_MODE_CREATE_INTERNAL]->set_tooltip_text(TTR("Create Internal Vertex")); + uv_button[UV_MODE_REMOVE_INTERNAL]->set_tooltip_text(TTR("Remove Internal Vertex")); + uv_button[UV_MODE_EDIT_POINT]->set_tooltip_text(TTR("Move Points") + "\n" + TTR("Ctrl: Rotate") + "\n" + TTR("Shift: Move All") + "\n" + TTR("Shift+Ctrl: Scale")); + uv_button[UV_MODE_MOVE]->set_tooltip_text(TTR("Move Polygon")); + uv_button[UV_MODE_ROTATE]->set_tooltip_text(TTR("Rotate Polygon")); + uv_button[UV_MODE_SCALE]->set_tooltip_text(TTR("Scale Polygon")); + uv_button[UV_MODE_ADD_POLYGON]->set_tooltip_text(TTR("Create a custom polygon. Enables custom polygon rendering.")); + uv_button[UV_MODE_REMOVE_POLYGON]->set_tooltip_text(TTR("Remove a custom polygon. If none remain, custom polygon rendering is disabled.")); + uv_button[UV_MODE_PAINT_WEIGHT]->set_tooltip_text(TTR("Paint weights with specified intensity.")); + uv_button[UV_MODE_CLEAR_WEIGHT]->set_tooltip_text(TTR("Unpaint weights with specified intensity.")); uv_button[UV_MODE_CREATE]->hide(); uv_button[UV_MODE_CREATE_INTERNAL]->hide(); @@ -1349,7 +1370,7 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : b_snap_enable->set_focus_mode(FOCUS_NONE); b_snap_enable->set_toggle_mode(true); b_snap_enable->set_pressed(use_snap); - b_snap_enable->set_tooltip(TTR("Enable Snap")); + b_snap_enable->set_tooltip_text(TTR("Enable Snap")); b_snap_enable->connect("toggled", callable_mp(this, &Polygon2DEditor::_set_use_snap)); b_snap_grid = memnew(Button); @@ -1359,7 +1380,7 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : b_snap_grid->set_focus_mode(FOCUS_NONE); b_snap_grid->set_toggle_mode(true); b_snap_grid->set_pressed(snap_show_grid); - b_snap_grid->set_tooltip(TTR("Show Grid")); + b_snap_grid->set_tooltip_text(TTR("Show Grid")); b_snap_grid->connect("toggled", callable_mp(this, &Polygon2DEditor::_set_show_grid)); grid_settings = memnew(AcceptDialog); @@ -1448,8 +1469,13 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : bone_scroll_vb = memnew(VBoxContainer); bone_scroll->add_child(bone_scroll_vb); + uv_panner.instantiate(); + uv_panner->set_callbacks(callable_mp(this, &Polygon2DEditor::_uv_pan_callback), callable_mp(this, &Polygon2DEditor::_uv_zoom_callback)); + uv_edit_draw->connect("draw", callable_mp(this, &Polygon2DEditor::_uv_draw)); uv_edit_draw->connect("gui_input", callable_mp(this, &Polygon2DEditor::_uv_input)); + uv_edit_draw->connect("focus_exited", callable_mp(uv_panner.ptr(), &ViewPanner::release_pan_key)); + uv_edit_draw->set_focus_mode(FOCUS_CLICK); uv_draw_zoom = 1.0; point_drag_index = -1; uv_drag = false; @@ -1463,6 +1489,6 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : uv_edit_draw->set_clip_contents(true); } -Polygon2DEditorPlugin::Polygon2DEditorPlugin(EditorNode *p_node) : - AbstractPolygon2DEditorPlugin(p_node, memnew(Polygon2DEditor(p_node)), "Polygon2D") { +Polygon2DEditorPlugin::Polygon2DEditorPlugin() : + AbstractPolygon2DEditorPlugin(memnew(Polygon2DEditor), "Polygon2D") { } diff --git a/editor/plugins/polygon_2d_editor_plugin.h b/editor/plugins/polygon_2d_editor_plugin.h index a04179dcad..2c55a5f631 100644 --- a/editor/plugins/polygon_2d_editor_plugin.h +++ b/editor/plugins/polygon_2d_editor_plugin.h @@ -1,38 +1,49 @@ -/*************************************************************************/ -/* polygon_2d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* polygon_2d_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 POLYGON_2D_EDITOR_PLUGIN_H #define POLYGON_2D_EDITOR_PLUGIN_H #include "editor/plugins/abstract_polygon_2d_editor.h" -#include "scene/gui/scroll_container.h" + +class AcceptDialog; +class ButtonGroup; +class HScrollBar; +class HSlider; +class MenuButton; +class Panel; +class ScrollContainer; +class SpinBox; +class TextureRect; +class ViewPanner; +class VScrollBar; class Polygon2DEditor : public AbstractPolygon2DEditor { GDCLASS(Polygon2DEditor, AbstractPolygon2DEditor); @@ -63,33 +74,37 @@ class Polygon2DEditor : public AbstractPolygon2DEditor { Button *uv_edit_mode[4]; Ref<ButtonGroup> uv_edit_group; - Polygon2D *node; + Polygon2D *node = nullptr; UVMode uv_mode; - AcceptDialog *uv_edit; + AcceptDialog *uv_edit = nullptr; Button *uv_button[UV_MODE_MAX]; - Button *b_snap_enable; - Button *b_snap_grid; - Panel *uv_edit_draw; - HSlider *uv_zoom; - SpinBox *uv_zoom_value; - HScrollBar *uv_hscroll; - VScrollBar *uv_vscroll; - MenuButton *uv_menu; - TextureRect *uv_icon_zoom; - - VBoxContainer *bone_scroll_main_vb; - ScrollContainer *bone_scroll; - VBoxContainer *bone_scroll_vb; - Button *sync_bones; - HSlider *bone_paint_strength; - SpinBox *bone_paint_radius; - Label *bone_paint_radius_label; + Button *b_snap_enable = nullptr; + Button *b_snap_grid = nullptr; + Panel *uv_edit_draw = nullptr; + HSlider *uv_zoom = nullptr; + SpinBox *uv_zoom_value = nullptr; + HScrollBar *uv_hscroll = nullptr; + VScrollBar *uv_vscroll = nullptr; + MenuButton *uv_menu = nullptr; + TextureRect *uv_icon_zoom = nullptr; + + Ref<ViewPanner> uv_panner; + void _uv_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event); + void _uv_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event); + + VBoxContainer *bone_scroll_main_vb = nullptr; + ScrollContainer *bone_scroll = nullptr; + VBoxContainer *bone_scroll_vb = nullptr; + Button *sync_bones = nullptr; + HSlider *bone_paint_strength = nullptr; + SpinBox *bone_paint_radius = nullptr; + Label *bone_paint_radius_label = nullptr; bool bone_painting; - int bone_painting_bone; + int bone_painting_bone = 0; Vector<float> prev_weights; Vector2 bone_paint_pos; - AcceptDialog *grid_settings; + AcceptDialog *grid_settings = nullptr; void _sync_bones(); void _update_bone_list(); @@ -100,7 +115,7 @@ class Polygon2DEditor : public AbstractPolygon2DEditor { Vector<Vector2> uv_create_uv_prev; Vector<Vector2> uv_create_poly_prev; Vector<Color> uv_create_colors_prev; - int uv_create_prev_internal_vertices; + int uv_create_prev_internal_vertices = 0; Array uv_create_bones_prev; Array polygons_prev; @@ -113,9 +128,9 @@ class Polygon2DEditor : public AbstractPolygon2DEditor { Vector2 uv_drag_from; bool updating_uv_scroll; - AcceptDialog *error; + AcceptDialog *error = nullptr; - Button *button_uv; + Button *button_uv = nullptr; bool use_snap; bool snap_show_grid; @@ -160,14 +175,14 @@ protected: Vector2 snap_point(Vector2 p_target) const; public: - Polygon2DEditor(EditorNode *p_editor); + Polygon2DEditor(); }; class Polygon2DEditorPlugin : public AbstractPolygon2DEditorPlugin { GDCLASS(Polygon2DEditorPlugin, AbstractPolygon2DEditorPlugin); public: - Polygon2DEditorPlugin(EditorNode *p_node); + Polygon2DEditorPlugin(); }; #endif // POLYGON_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.cpp b/editor/plugins/polygon_3d_editor_plugin.cpp index bf6485f9ec..9defb4de9b 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.cpp +++ b/editor/plugins/polygon_3d_editor_plugin.cpp @@ -1,53 +1,58 @@ -/*************************************************************************/ -/* collision_polygon_3d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "collision_polygon_3d_editor_plugin.h" +/**************************************************************************/ +/* polygon_3d_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 "polygon_3d_editor_plugin.h" #include "canvas_item_editor_plugin.h" +#include "core/core_string_names.h" #include "core/input/input.h" #include "core/io/file_access.h" #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "node_3d_editor_plugin.h" #include "scene/3d/camera_3d.h" +#include "scene/gui/separator.h" -void CollisionPolygon3DEditor::_notification(int p_what) { +void Polygon3DEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { button_create->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); button_edit->set_icon(get_theme_icon(SNAME("MovePoint"), SNAME("EditorIcons"))); button_edit->set_pressed(true); - get_tree()->connect("node_removed", callable_mp(this, &CollisionPolygon3DEditor::_node_removed)); + get_tree()->connect("node_removed", callable_mp(this, &Polygon3DEditor::_node_removed)); } break; + case NOTIFICATION_PROCESS: { if (!node) { return; @@ -62,7 +67,7 @@ void CollisionPolygon3DEditor::_notification(int p_what) { } } -void CollisionPolygon3DEditor::_node_removed(Node *p_node) { +void Polygon3DEditor::_node_removed(Node *p_node) { if (p_node == node) { node = nullptr; if (imgeom->get_parent() == p_node) { @@ -73,7 +78,7 @@ void CollisionPolygon3DEditor::_node_removed(Node *p_node) { } } -void CollisionPolygon3DEditor::_menu_option(int p_option) { +void Polygon3DEditor::_menu_option(int p_option) { switch (p_option) { case MODE_CREATE: { mode = MODE_CREATE; @@ -88,10 +93,13 @@ void CollisionPolygon3DEditor::_menu_option(int p_option) { } } -void CollisionPolygon3DEditor::_wip_close() { +void Polygon3DEditor::_wip_close() { + Object *obj = node_resource.is_valid() ? (Object *)node_resource.ptr() : node; + ERR_FAIL_COND_MSG(!obj, "Edited object is not valid."); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Create Polygon3D")); - undo_redo->add_undo_method(node, "set_polygon", node->call("get_polygon")); - undo_redo->add_do_method(node, "set_polygon", wip); + undo_redo->add_undo_method(obj, "set_polygon", obj->call("get_polygon")); + undo_redo->add_do_method(obj, "set_polygon", wip); undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); wip.clear(); @@ -103,15 +111,16 @@ void CollisionPolygon3DEditor::_wip_close() { undo_redo->commit_action(); } -EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput Polygon3DEditor::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!node) { return EditorPlugin::AFTER_GUI_INPUT_PASS; } + Object *obj = node_resource.is_valid() ? (Object *)node_resource.ptr() : node; Transform3D gt = node->get_global_transform(); Transform3D gi = gt.affine_inverse(); float depth = _get_depth() * 0.5; - Vector3 n = gt.basis.get_axis(2).normalized(); + Vector3 n = gt.basis.get_column(2).normalized(); Plane p(n, gt.origin + n * depth); Ref<InputEventMouseButton> mb = p_event; @@ -135,7 +144,7 @@ EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input( //Let the snap happen when the point is being moved, instead. //cpoint = CanvasItemEditor::get_singleton()->snap_point(cpoint); - Vector<Vector2> poly = node->call("get_polygon"); + PackedVector2Array poly = _get_polygon(); //first check if a point is to be added (segment split) real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); @@ -177,10 +186,11 @@ EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input( if (mb->is_pressed()) { if (mb->is_ctrl_pressed()) { if (poly.size() < 3) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Edit Poly")); - undo_redo->add_undo_method(node, "set_polygon", poly); + undo_redo->add_undo_method(obj, "set_polygon", poly); poly.push_back(cpoint); - undo_redo->add_do_method(node, "set_polygon", poly); + undo_redo->add_do_method(obj, "set_polygon", poly); undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); undo_redo->commit_action(); @@ -215,7 +225,7 @@ EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input( poly.insert(closest_idx + 1, cpoint); edited_point = closest_idx + 1; edited_point_pos = cpoint; - node->call("set_polygon", poly); + _set_polygon(poly); _polygon_draw(); snap_ignore = true; @@ -255,9 +265,10 @@ EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input( ERR_FAIL_INDEX_V(edited_point, poly.size(), EditorPlugin::AFTER_GUI_INPUT_PASS); poly.write[edited_point] = edited_point_pos; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Edit Poly")); - undo_redo->add_do_method(node, "set_polygon", poly); - undo_redo->add_undo_method(node, "set_polygon", pre_move_edit); + undo_redo->add_do_method(obj, "set_polygon", poly); + undo_redo->add_undo_method(obj, "set_polygon", pre_move_edit); undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); undo_redo->commit_action(); @@ -283,10 +294,11 @@ EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input( } if (closest_idx >= 0) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Edit Poly (Remove Point)")); - undo_redo->add_undo_method(node, "set_polygon", poly); + undo_redo->add_undo_method(obj, "set_polygon", poly); poly.remove_at(closest_idx); - undo_redo->add_do_method(node, "set_polygon", poly); + undo_redo->add_do_method(obj, "set_polygon", poly); undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); undo_redo->commit_action(); @@ -301,7 +313,7 @@ EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input( Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - if (edited_point != -1 && (wip_active || (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE)) { + if (edited_point != -1 && (wip_active || mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { Vector2 gpoint = mm->get_position(); Vector3 ray_from = p_camera->project_ray_origin(gpoint); @@ -335,29 +347,45 @@ EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input( return EditorPlugin::AFTER_GUI_INPUT_PASS; } -float CollisionPolygon3DEditor::_get_depth() { - if (bool(node->call("_has_editable_3d_polygon_no_depth"))) { - return 0; +float Polygon3DEditor::_get_depth() { + Object *obj = node_resource.is_valid() ? (Object *)node_resource.ptr() : node; + ERR_FAIL_COND_V_MSG(!obj, 0.0f, "Edited object is not valid."); + + if (bool(obj->call("_has_editable_3d_polygon_no_depth"))) { + return 0.0f; } - return float(node->call("get_depth")); + return float(obj->call("get_depth")); } -void CollisionPolygon3DEditor::_polygon_draw() { +PackedVector2Array Polygon3DEditor::_get_polygon() { + Object *obj = node_resource.is_valid() ? (Object *)node_resource.ptr() : node; + ERR_FAIL_COND_V_MSG(!obj, PackedVector2Array(), "Edited object is not valid."); + return PackedVector2Array(obj->call("get_polygon")); +} + +void Polygon3DEditor::_set_polygon(PackedVector2Array p_poly) { + Object *obj = node_resource.is_valid() ? (Object *)node_resource.ptr() : node; + ERR_FAIL_COND_MSG(!obj, "Edited object is not valid."); + obj->call("set_polygon", p_poly); +} + +void Polygon3DEditor::_polygon_draw() { if (!node) { return; } - Vector<Vector2> poly; + PackedVector2Array poly; if (wip_active) { poly = wip; } else { - poly = node->call("get_polygon"); + poly = _get_polygon(); } float depth = _get_depth() * 0.5; + m->clear_surfaces(); imesh->clear_surfaces(); imgeom->set_material_override(line_material); imesh->surface_begin(Mesh::PRIMITIVE_LINES); @@ -463,23 +491,32 @@ void CollisionPolygon3DEditor::_polygon_draw() { m->surface_set_material(0, handle_material); } -void CollisionPolygon3DEditor::edit(Node *p_collision_polygon) { - if (p_collision_polygon) { - node = Object::cast_to<Node3D>(p_collision_polygon); +void Polygon3DEditor::edit(Node *p_node) { + if (p_node) { + node = Object::cast_to<Node3D>(p_node); + node_resource = node->call("_get_editable_3d_polygon_resource"); + + if (node_resource.is_valid()) { + node_resource->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Polygon3DEditor::_polygon_draw)); + } //Enable the pencil tool if the polygon is empty - if (Vector<Vector2>(node->call("get_polygon")).size() == 0) { + if (_get_polygon().is_empty()) { _menu_option(MODE_CREATE); } wip.clear(); wip_active = false; edited_point = -1; - p_collision_polygon->add_child(imgeom); + p_node->add_child(imgeom); _polygon_draw(); set_process(true); prev_depth = -1; } else { node = nullptr; + if (node_resource.is_valid()) { + node_resource->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Polygon3DEditor::_polygon_draw)); + } + node_resource.unref(); if (imgeom->get_parent()) { imgeom->get_parent()->remove_child(imgeom); @@ -489,26 +526,24 @@ void CollisionPolygon3DEditor::edit(Node *p_collision_polygon) { } } -void CollisionPolygon3DEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_polygon_draw"), &CollisionPolygon3DEditor::_polygon_draw); +void Polygon3DEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_polygon_draw"), &Polygon3DEditor::_polygon_draw); } -CollisionPolygon3DEditor::CollisionPolygon3DEditor(EditorNode *p_editor) { +Polygon3DEditor::Polygon3DEditor() { node = nullptr; - editor = p_editor; - undo_redo = EditorNode::get_undo_redo(); add_child(memnew(VSeparator)); button_create = memnew(Button); button_create->set_flat(true); add_child(button_create); - button_create->connect("pressed", callable_mp(this, &CollisionPolygon3DEditor::_menu_option), varray(MODE_CREATE)); + button_create->connect("pressed", callable_mp(this, &Polygon3DEditor::_menu_option).bind(MODE_CREATE)); button_create->set_toggle_mode(true); button_edit = memnew(Button); button_edit->set_flat(true); add_child(button_edit); - button_edit->connect("pressed", callable_mp(this, &CollisionPolygon3DEditor::_menu_option), varray(MODE_EDIT)); + button_edit->connect("pressed", callable_mp(this, &Polygon3DEditor::_menu_option).bind(MODE_EDIT)); button_edit->set_toggle_mode(true); mode = MODE_EDIT; @@ -531,7 +566,7 @@ CollisionPolygon3DEditor::CollisionPolygon3DEditor(EditorNode *p_editor) { handle_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); handle_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); handle_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); - Ref<Texture2D> handle = editor->get_gui_base()->get_theme_icon(SNAME("Editor3DHandle"), SNAME("EditorIcons")); + Ref<Texture2D> handle = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Editor3DHandle"), SNAME("EditorIcons")); handle_material->set_point_size(handle->get_width()); handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle); @@ -544,12 +579,12 @@ CollisionPolygon3DEditor::CollisionPolygon3DEditor(EditorNode *p_editor) { snap_ignore = false; } -CollisionPolygon3DEditor::~CollisionPolygon3DEditor() { +Polygon3DEditor::~Polygon3DEditor() { memdelete(imgeom); } void Polygon3DEditorPlugin::edit(Object *p_object) { - collision_polygon_editor->edit(Object::cast_to<Node>(p_object)); + polygon_editor->edit(Object::cast_to<Node>(p_object)); } bool Polygon3DEditorPlugin::handles(Object *p_object) const { @@ -558,19 +593,18 @@ bool Polygon3DEditorPlugin::handles(Object *p_object) const { void Polygon3DEditorPlugin::make_visible(bool p_visible) { if (p_visible) { - collision_polygon_editor->show(); + polygon_editor->show(); } else { - collision_polygon_editor->hide(); - collision_polygon_editor->edit(nullptr); + polygon_editor->hide(); + polygon_editor->edit(nullptr); } } -Polygon3DEditorPlugin::Polygon3DEditorPlugin(EditorNode *p_node) { - editor = p_node; - collision_polygon_editor = memnew(CollisionPolygon3DEditor(p_node)); - Node3DEditor::get_singleton()->add_control_to_menu_panel(collision_polygon_editor); +Polygon3DEditorPlugin::Polygon3DEditorPlugin() { + polygon_editor = memnew(Polygon3DEditor); + Node3DEditor::get_singleton()->add_control_to_menu_panel(polygon_editor); - collision_polygon_editor->hide(); + polygon_editor->hide(); } Polygon3DEditorPlugin::~Polygon3DEditorPlugin() { diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.h b/editor/plugins/polygon_3d_editor_plugin.h index cd8c857398..6cb9275dd6 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.h +++ b/editor/plugins/polygon_3d_editor_plugin.h @@ -1,48 +1,48 @@ -/*************************************************************************/ -/* collision_polygon_3d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 COLLISION_POLYGON_EDITOR_PLUGIN_H -#define COLLISION_POLYGON_EDITOR_PLUGIN_H - -#include "editor/editor_node.h" +/**************************************************************************/ +/* polygon_3d_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 POLYGON_3D_EDITOR_PLUGIN_H +#define POLYGON_3D_EDITOR_PLUGIN_H + #include "editor/editor_plugin.h" #include "scene/3d/collision_polygon_3d.h" #include "scene/3d/mesh_instance_3d.h" +#include "scene/gui/box_container.h" #include "scene/resources/immediate_mesh.h" class CanvasItemEditor; +class MenuButton; -class CollisionPolygon3DEditor : public HBoxContainer { - GDCLASS(CollisionPolygon3DEditor, HBoxContainer); +class Polygon3DEditor : public HBoxContainer { + GDCLASS(Polygon3DEditor, HBoxContainer); - UndoRedo *undo_redo; enum Mode { MODE_CREATE, MODE_EDIT, @@ -51,36 +51,38 @@ class CollisionPolygon3DEditor : public HBoxContainer { Mode mode; - Button *button_create; - Button *button_edit; + Button *button_create = nullptr; + Button *button_edit = nullptr; Ref<StandardMaterial3D> line_material; Ref<StandardMaterial3D> handle_material; - EditorNode *editor; - Panel *panel; - Node3D *node; + Panel *panel = nullptr; + Node3D *node = nullptr; + Ref<Resource> node_resource; Ref<ImmediateMesh> imesh; - MeshInstance3D *imgeom; - MeshInstance3D *pointsm; + MeshInstance3D *imgeom = nullptr; + MeshInstance3D *pointsm = nullptr; Ref<ArrayMesh> m; - MenuButton *options; + MenuButton *options = nullptr; - int edited_point; + int edited_point = 0; Vector2 edited_point_pos; - Vector<Vector2> pre_move_edit; - Vector<Vector2> wip; + PackedVector2Array pre_move_edit; + PackedVector2Array wip; bool wip_active; bool snap_ignore; - float prev_depth; + float prev_depth = 0.0f; void _wip_close(); void _polygon_draw(); void _menu_option(int p_option); float _get_depth(); + PackedVector2Array _get_polygon(); + void _set_polygon(PackedVector2Array p_poly); protected: void _notification(int p_what); @@ -88,20 +90,19 @@ protected: static void _bind_methods(); public: - virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); - void edit(Node *p_collision_polygon); - CollisionPolygon3DEditor(EditorNode *p_editor); - ~CollisionPolygon3DEditor(); + virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); + void edit(Node *p_node); + Polygon3DEditor(); + ~Polygon3DEditor(); }; class Polygon3DEditorPlugin : public EditorPlugin { GDCLASS(Polygon3DEditorPlugin, EditorPlugin); - CollisionPolygon3DEditor *collision_polygon_editor; - EditorNode *editor; + Polygon3DEditor *polygon_editor = nullptr; public: - virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); } + virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return polygon_editor->forward_3d_gui_input(p_camera, p_event); } virtual String get_name() const override { return "Polygon3DEditor"; } bool has_main_screen() const override { return false; } @@ -109,8 +110,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - Polygon3DEditorPlugin(EditorNode *p_node); + Polygon3DEditorPlugin(); ~Polygon3DEditorPlugin(); }; -#endif // COLLISION_POLYGON_EDITOR_PLUGIN_H +#endif // POLYGON_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp index d5287bc2fb..dcbff2c756 100644 --- a/editor/plugins/resource_preloader_editor_plugin.cpp +++ b/editor/plugins/resource_preloader_editor_plugin.cpp @@ -1,50 +1,49 @@ -/*************************************************************************/ -/* resource_preloader_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* resource_preloader_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 "resource_preloader_editor_plugin.h" #include "core/config/project_settings.h" #include "core/io/resource_loader.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" void ResourcePreloaderEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - load->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); - } - - if (p_what == NOTIFICATION_READY) { - //NodePath("/root")->connect("node_removed", this,"_node_removed",Vector<Variant>(),true); - } - - if (p_what == NOTIFICATION_DRAW) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + load->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); + } break; } } @@ -52,14 +51,14 @@ void ResourcePreloaderEditor::_files_load_request(const Vector<String> &p_paths) for (int i = 0; i < p_paths.size(); i++) { String path = p_paths[i]; - RES resource; + Ref<Resource> resource; resource = ResourceLoader::load(path); if (resource.is_null()) { dialog->set_text(TTR("ERROR: Couldn't load resource!")); dialog->set_title(TTR("Error!")); //dialog->get_cancel()->set_text("Close"); - dialog->get_ok_button()->set_text(TTR("Close")); + dialog->set_ok_button_text(TTR("Close")); dialog->popup_centered(); return; ///beh should show an error i guess } @@ -72,6 +71,7 @@ void ResourcePreloaderEditor::_files_load_request(const Vector<String> &p_paths) name = basename + " " + itos(counter); } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Resource")); undo_redo->add_do_method(preloader, "add_resource", name, resource); undo_redo->add_undo_method(preloader, "remove_resource", name); @@ -110,12 +110,13 @@ void ResourcePreloaderEditor::_item_edited() { return; } - if (new_name.is_empty() || new_name.find("\\") != -1 || new_name.find("/") != -1 || preloader->has_resource(new_name)) { + if (new_name.is_empty() || new_name.contains("\\") || new_name.contains("/") || preloader->has_resource(new_name)) { s->set_text(0, old_name); return; } - RES samp = preloader->get_resource(old_name); + Ref<Resource> samp = preloader->get_resource(old_name); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Rename Resource")); undo_redo->add_do_method(preloader, "remove_resource", old_name); undo_redo->add_do_method(preloader, "add_resource", new_name, samp); @@ -128,6 +129,7 @@ void ResourcePreloaderEditor::_item_edited() { } void ResourcePreloaderEditor::_remove_resource(const String &p_to_remove) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Delete Resource")); undo_redo->add_do_method(preloader, "remove_resource", p_to_remove); undo_redo->add_undo_method(preloader, "add_resource", p_to_remove, preloader->get_resource(p_to_remove)); @@ -137,11 +139,11 @@ void ResourcePreloaderEditor::_remove_resource(const String &p_to_remove) { } void ResourcePreloaderEditor::_paste_pressed() { - RES r = EditorSettings::get_singleton()->get_resource_clipboard(); + Ref<Resource> r = EditorSettings::get_singleton()->get_resource_clipboard(); if (!r.is_valid()) { dialog->set_text(TTR("Resource clipboard is empty!")); dialog->set_title(TTR("Error!")); - dialog->get_ok_button()->set_text(TTR("Close")); + dialog->set_ok_button_text(TTR("Close")); dialog->popup_centered(); return; ///beh should show an error i guess } @@ -161,6 +163,7 @@ void ResourcePreloaderEditor::_paste_pressed() { name = basename + " " + itos(counter); } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Paste Resource")); undo_redo->add_do_method(preloader, "add_resource", name, r); undo_redo->add_undo_method(preloader, "remove_resource", name); @@ -192,13 +195,13 @@ void ResourcePreloaderEditor::_update_library() { ti->set_text(0, E); ti->set_metadata(0, E); - RES r = preloader->get_resource(E); + Ref<Resource> r = preloader->get_resource(E); ERR_CONTINUE(r.is_null()); String type = r->get_class(); ti->set_icon(0, EditorNode::get_singleton()->get_class_icon(type, "Object")); - ti->set_tooltip(0, TTR("Instance:") + " " + r->get_path() + "\n" + TTR("Type:") + " " + type); + ti->set_tooltip_text(0, TTR("Instance:") + " " + r->get_path() + "\n" + TTR("Type:") + " " + type); ti->set_text(1, r->get_path()); ti->set_editable(1, false); @@ -215,7 +218,11 @@ void ResourcePreloaderEditor::_update_library() { //player->add_resource("default",resource); } -void ResourcePreloaderEditor::_cell_button_pressed(Object *p_item, int p_column, int p_id) { +void ResourcePreloaderEditor::_cell_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) { + if (p_button != MouseButton::LEFT) { + return; + } + TreeItem *item = Object::cast_to<TreeItem>(p_item); ERR_FAIL_COND(!item); @@ -224,7 +231,7 @@ void ResourcePreloaderEditor::_cell_button_pressed(Object *p_item, int p_column, EditorInterface::get_singleton()->open_scene_from_path(rpath); } else if (p_id == BUTTON_EDIT_RESOURCE) { - RES r = preloader->get_resource(item->get_text(0)); + Ref<Resource> r = preloader->get_resource(item->get_text(0)); EditorInterface::get_singleton()->edit_resource(r); } else if (p_id == BUTTON_REMOVE) { @@ -251,7 +258,7 @@ Variant ResourcePreloaderEditor::get_drag_data_fw(const Point2 &p_point, Control String name = ti->get_metadata(0); - RES res = preloader->get_resource(name); + Ref<Resource> res = preloader->get_resource(name); if (!res.is_valid()) { return Variant(); } @@ -271,7 +278,7 @@ bool ResourcePreloaderEditor::can_drop_data_fw(const Point2 &p_point, const Vari } if (String(d["type"]) == "resource" && d.has("resource")) { - RES r = d["resource"]; + Ref<Resource> r = d["resource"]; return r.is_valid(); } @@ -296,7 +303,7 @@ void ResourcePreloaderEditor::drop_data_fw(const Point2 &p_point, const Variant } if (String(d["type"]) == "resource" && d.has("resource")) { - RES r = d["resource"]; + Ref<Resource> r = d["resource"]; if (r.is_valid()) { String basename; @@ -315,6 +322,7 @@ void ResourcePreloaderEditor::drop_data_fw(const Point2 &p_point, const Variant name = basename + "_" + itos(counter); } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Resource")); undo_redo->add_do_method(preloader, "add_resource", name, r); undo_redo->add_undo_method(preloader, "remove_resource", name); @@ -334,10 +342,6 @@ void ResourcePreloaderEditor::drop_data_fw(const Point2 &p_point, const Variant void ResourcePreloaderEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_library"), &ResourcePreloaderEditor::_update_library); ClassDB::bind_method(D_METHOD("_remove_resource", "to_remove"), &ResourcePreloaderEditor::_remove_resource); - - ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &ResourcePreloaderEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &ResourcePreloaderEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("_drop_data_fw"), &ResourcePreloaderEditor::drop_data_fw); } ResourcePreloaderEditor::ResourcePreloaderEditor() { @@ -350,7 +354,7 @@ ResourcePreloaderEditor::ResourcePreloaderEditor() { vbc->add_child(hbc); load = memnew(Button); - load->set_tooltip(TTR("Load Resource")); + load->set_tooltip_text(TTR("Load Resource")); hbc->add_child(load); paste = memnew(Button); @@ -361,7 +365,7 @@ ResourcePreloaderEditor::ResourcePreloaderEditor() { add_child(file); tree = memnew(Tree); - tree->connect("button_pressed", callable_mp(this, &ResourcePreloaderEditor::_cell_button_pressed)); + tree->connect("button_clicked", callable_mp(this, &ResourcePreloaderEditor::_cell_button_pressed)); tree->set_columns(2); tree->set_column_expand_ratio(0, 2); tree->set_column_clip_content(0, true); @@ -371,7 +375,7 @@ ResourcePreloaderEditor::ResourcePreloaderEditor() { tree->set_column_expand(1, true); tree->set_v_size_flags(SIZE_EXPAND_FILL); - tree->set_drag_forwarding(this); + SET_DRAG_FORWARDING_GCD(tree, ResourcePreloaderEditor); vbc->add_child(tree); dialog = memnew(AcceptDialog); @@ -385,7 +389,6 @@ ResourcePreloaderEditor::ResourcePreloaderEditor() { } void ResourcePreloaderEditorPlugin::edit(Object *p_object) { - preloader_editor->set_undo_redo(&get_undo_redo()); ResourcePreloader *s = Object::cast_to<ResourcePreloader>(p_object); if (!s) { return; @@ -402,11 +405,11 @@ void ResourcePreloaderEditorPlugin::make_visible(bool p_visible) { if (p_visible) { //preloader_editor->show(); button->show(); - editor->make_bottom_panel_item_visible(preloader_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(preloader_editor); //preloader_editor->set_process(true); } else { if (preloader_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); } button->hide(); //preloader_editor->hide(); @@ -414,16 +417,12 @@ void ResourcePreloaderEditorPlugin::make_visible(bool p_visible) { } } -ResourcePreloaderEditorPlugin::ResourcePreloaderEditorPlugin(EditorNode *p_node) { - editor = p_node; +ResourcePreloaderEditorPlugin::ResourcePreloaderEditorPlugin() { preloader_editor = memnew(ResourcePreloaderEditor); preloader_editor->set_custom_minimum_size(Size2(0, 250) * EDSCALE); - button = editor->add_bottom_panel_item(TTR("ResourcePreloader"), preloader_editor); + button = EditorNode::get_singleton()->add_bottom_panel_item("ResourcePreloader", preloader_editor); button->hide(); - - //preloader_editor->set_anchor( MARGIN_TOP, Control::ANCHOR_END); - //preloader_editor->set_margin( MARGIN_TOP, 120 ); } ResourcePreloaderEditorPlugin::~ResourcePreloaderEditorPlugin() { diff --git a/editor/plugins/resource_preloader_editor_plugin.h b/editor/plugins/resource_preloader_editor_plugin.h index 838d72df41..7a4cabbb69 100644 --- a/editor/plugins/resource_preloader_editor_plugin.h +++ b/editor/plugins/resource_preloader_editor_plugin.h @@ -1,43 +1,44 @@ -/*************************************************************************/ -/* resource_preloader_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* resource_preloader_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 RESOURCE_PRELOADER_EDITOR_PLUGIN_H #define RESOURCE_PRELOADER_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/gui/dialogs.h" -#include "scene/gui/file_dialog.h" +#include "scene/gui/panel_container.h" #include "scene/gui/tree.h" #include "scene/main/resource_preloader.h" +class EditorFileDialog; + class ResourcePreloaderEditor : public PanelContainer { GDCLASS(ResourcePreloaderEditor, PanelContainer); @@ -47,27 +48,25 @@ class ResourcePreloaderEditor : public PanelContainer { BUTTON_REMOVE }; - Button *load; - Button *paste; - Tree *tree; + Button *load = nullptr; + Button *paste = nullptr; + Tree *tree = nullptr; bool loading_scene; - EditorFileDialog *file; + EditorFileDialog *file = nullptr; - AcceptDialog *dialog; + AcceptDialog *dialog = nullptr; - ResourcePreloader *preloader; + ResourcePreloader *preloader = nullptr; void _load_pressed(); void _files_load_request(const Vector<String> &p_paths); void _paste_pressed(); void _remove_resource(const String &p_to_remove); void _update_library(); - void _cell_button_pressed(Object *p_item, int p_column, int p_id); + void _cell_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button); void _item_edited(); - UndoRedo *undo_redo; - Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); @@ -78,8 +77,6 @@ protected: static void _bind_methods(); public: - void set_undo_redo(UndoRedo *p_undo_redo) { undo_redo = p_undo_redo; } - void edit(ResourcePreloader *p_preloader); ResourcePreloaderEditor(); }; @@ -87,9 +84,8 @@ public: class ResourcePreloaderEditorPlugin : public EditorPlugin { GDCLASS(ResourcePreloaderEditorPlugin, EditorPlugin); - ResourcePreloaderEditor *preloader_editor; - EditorNode *editor; - Button *button; + ResourcePreloaderEditor *preloader_editor = nullptr; + Button *button = nullptr; public: virtual String get_name() const override { return "ResourcePreloader"; } @@ -98,7 +94,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - ResourcePreloaderEditorPlugin(EditorNode *p_node); + ResourcePreloaderEditorPlugin(); ~ResourcePreloaderEditorPlugin(); }; diff --git a/editor/plugins/root_motion_editor_plugin.cpp b/editor/plugins/root_motion_editor_plugin.cpp index 34b39d2a17..e8abecd115 100644 --- a/editor/plugins/root_motion_editor_plugin.cpp +++ b/editor/plugins/root_motion_editor_plugin.cpp @@ -1,35 +1,38 @@ -/*************************************************************************/ -/* root_motion_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* root_motion_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 "root_motion_editor_plugin.h" #include "editor/editor_node.h" +#include "scene/animation/animation_player.h" +#include "scene/animation/animation_tree.h" +#include "scene/gui/tree.h" #include "scene/main/window.h" void EditorPropertyRootMotion::_confirmed() { @@ -45,8 +48,6 @@ void EditorPropertyRootMotion::_confirmed() { } void EditorPropertyRootMotion::_node_assign() { - NodePath current = get_edited_object()->get(get_edited_property()); - AnimationTree *atree = Object::cast_to<AnimationTree>(get_edited_object()); if (!atree->has_node(atree->get_animation_player())) { EditorNode::get_singleton()->show_warning(TTR("AnimationTree has no path set to an AnimationPlayer")); @@ -65,7 +66,7 @@ void EditorPropertyRootMotion::_node_assign() { return; } - Set<String> paths; + HashSet<String> paths; { List<StringName> animations; player->get_animation_list(&animations); @@ -73,7 +74,10 @@ void EditorPropertyRootMotion::_node_assign() { for (const StringName &E : animations) { Ref<Animation> anim = player->get_animation(E); for (int i = 0; i < anim->get_track_count(); i++) { - paths.insert(anim->track_get_path(i)); + String pathname = anim->track_get_path(i).get_concatenated_names(); + if (!paths.has(pathname)) { + paths.insert(pathname); + } } } } @@ -81,10 +85,10 @@ void EditorPropertyRootMotion::_node_assign() { filters->clear(); TreeItem *root = filters->create_item(); - Map<String, TreeItem *> parenthood; + HashMap<String, TreeItem *> parenthood; - for (Set<String>::Element *E = paths.front(); E; E = E->next()) { - NodePath path = E->get(); + for (const String &E : paths) { + NodePath path = E; TreeItem *ti = nullptr; String accum; for (int i = 0; i < path.get_name_count(); i++) { @@ -122,66 +126,33 @@ void EditorPropertyRootMotion::_node_assign() { continue; //no node, can't edit } - if (path.get_subname_count()) { - String concat = path.get_concatenated_subnames(); - - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node); - if (skeleton && skeleton->find_bone(concat) != -1) { - //path in skeleton - const String &bone = concat; - int idx = skeleton->find_bone(bone); - List<String> bone_path; - while (idx != -1) { - bone_path.push_front(skeleton->get_bone_name(idx)); - idx = skeleton->get_bone_parent(idx); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node); + if (skeleton) { + HashMap<int, TreeItem *> items; + items.insert(-1, ti); + Ref<Texture> bone_icon = get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons")); + Vector<int> bones_to_process = skeleton->get_parentless_bones(); + while (bones_to_process.size() > 0) { + int current_bone_idx = bones_to_process[0]; + bones_to_process.erase(current_bone_idx); + + Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx); + int child_bone_size = current_bone_child_bones.size(); + for (int i = 0; i < child_bone_size; i++) { + bones_to_process.push_back(current_bone_child_bones[i]); } - accum += ":"; - for (List<String>::Element *F = bone_path.front(); F; F = F->next()) { - if (F != bone_path.front()) { - accum += "/"; - } - - accum += F->get(); - if (!parenthood.has(accum)) { - ti = filters->create_item(ti); - parenthood[accum] = ti; - ti->set_text(0, F->get()); - ti->set_selectable(0, true); - ti->set_editable(0, false); - ti->set_icon(0, get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons"))); - ti->set_metadata(0, accum); - } else { - ti = parenthood[accum]; - } - } + const int parent_idx = skeleton->get_bone_parent(current_bone_idx); + TreeItem *parent_item = items.find(parent_idx)->value; - ti->set_selectable(0, true); - ti->set_text(0, concat); - ti->set_icon(0, get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons"))); - ti->set_metadata(0, path); - if (path == current) { - ti->select(0); - } + TreeItem *joint_item = filters->create_item(parent_item); + items.insert(current_bone_idx, joint_item); - } else { - //just a property - ti = filters->create_item(ti); - ti->set_text(0, concat); - ti->set_selectable(0, true); - ti->set_metadata(0, path); - if (path == current) { - ti->select(0); - } - } - } else { - if (ti) { - //just a node, likely call or animation track - ti->set_selectable(0, true); - ti->set_metadata(0, path); - if (path == current) { - ti->select(0); - } + joint_item->set_text(0, skeleton->get_bone_name(current_bone_idx)); + joint_item->set_icon(0, bone_icon); + joint_item->set_selectable(0, true); + joint_item->set_metadata(0, accum + ":" + skeleton->get_bone_name(current_bone_idx)); + joint_item->set_collapsed(true); } } } @@ -197,8 +168,7 @@ void EditorPropertyRootMotion::_node_clear() { void EditorPropertyRootMotion::update_property() { NodePath p = get_edited_object()->get(get_edited_property()); - - assign->set_tooltip(p); + assign->set_tooltip_text(p); if (p == NodePath()) { assign->set_icon(Ref<Texture2D>()); assign->set_text(TTR("Assign...")); @@ -206,26 +176,8 @@ void EditorPropertyRootMotion::update_property() { return; } - Node *base_node = nullptr; - if (base_hint != NodePath()) { - if (get_tree()->get_root()->has_node(base_hint)) { - base_node = get_tree()->get_root()->get_node(base_hint); - } - } else { - base_node = Object::cast_to<Node>(get_edited_object()); - } - - if (!base_node || !base_node->has_node(p)) { - assign->set_icon(Ref<Texture2D>()); - assign->set_text(p); - return; - } - - Node *target_node = base_node->get_node(p); - ERR_FAIL_COND(!target_node); - - assign->set_text(target_node->get_name()); - assign->set_icon(EditorNode::get_singleton()->get_object_icon(target_node, "Node")); + assign->set_icon(Ref<Texture2D>()); + assign->set_text(p); } void EditorPropertyRootMotion::setup(const NodePath &p_base_hint) { @@ -233,9 +185,12 @@ void EditorPropertyRootMotion::setup(const NodePath &p_base_hint) { } void EditorPropertyRootMotion::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - Ref<Texture2D> t = get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")); - clear->set_icon(t); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + Ref<Texture2D> t = get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")); + clear->set_icon(t); + } break; } } @@ -274,12 +229,9 @@ bool EditorInspectorRootMotionPlugin::can_handle(Object *p_object) { return true; // Can handle everything. } -bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { +bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) { if (p_path == "root_motion_track" && p_object->is_class("AnimationTree") && p_type == Variant::NODE_PATH) { EditorPropertyRootMotion *editor = memnew(EditorPropertyRootMotion); - if (p_hint == PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE && !p_hint_text.is_empty()) { - editor->setup(p_hint_text); - } add_property_editor(p_path, editor); return true; } diff --git a/editor/plugins/root_motion_editor_plugin.h b/editor/plugins/root_motion_editor_plugin.h index c2866f269b..d27f0d30cc 100644 --- a/editor/plugins/root_motion_editor_plugin.h +++ b/editor/plugins/root_motion_editor_plugin.h @@ -1,49 +1,48 @@ -/*************************************************************************/ -/* root_motion_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* root_motion_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 ROOT_MOTION_EDITOR_PLUGIN_H #define ROOT_MOTION_EDITOR_PLUGIN_H #include "editor/editor_inspector.h" -#include "editor/editor_spin_slider.h" -#include "editor/property_selector.h" -#include "scene/animation/animation_tree.h" + +class Tree; class EditorPropertyRootMotion : public EditorProperty { GDCLASS(EditorPropertyRootMotion, EditorProperty); - Button *assign; - Button *clear; + Button *assign = nullptr; + Button *clear = nullptr; NodePath base_hint; - ConfirmationDialog *filter_dialog; - Tree *filters; + ConfirmationDialog *filter_dialog = nullptr; + Tree *filters = nullptr; void _confirmed(); void _node_assign(); @@ -64,7 +63,7 @@ class EditorInspectorRootMotionPlugin : public EditorInspectorPlugin { public: virtual bool can_handle(Object *p_object) override; - virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false) override; }; #endif // ROOT_MOTION_EDITOR_PLUGIN_H diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 03ed0e0ef2..ccbc7c3d74 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -1,52 +1,58 @@ -/*************************************************************************/ -/* script_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* script_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 "script_editor_plugin.h" #include "core/config/project_settings.h" #include "core/input/input.h" #include "core/io/file_access.h" +#include "core/io/json.h" #include "core/io/resource_loader.h" #include "core/os/keyboard.h" #include "core/os/os.h" +#include "core/version.h" #include "editor/debugger/editor_debugger_node.h" #include "editor/debugger/script_editor_debugger.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_help_search.h" #include "editor/editor_node.h" +#include "editor/editor_paths.h" #include "editor/editor_run_script.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/filesystem_dock.h" #include "editor/find_in_files.h" +#include "editor/inspector_dock.h" #include "editor/node_dock.h" #include "editor/plugins/shader_editor_plugin.h" -#include "modules/visual_script/editor/visual_script_editor.h" +#include "editor/plugins/text_shader_editor.h" #include "scene/main/window.h" #include "scene/scene_string_names.h" #include "script_text_editor.h" @@ -56,19 +62,15 @@ /*** SYNTAX HIGHLIGHTER ****/ String EditorSyntaxHighlighter::_get_name() const { - String ret; - if (GDVIRTUAL_CALL(_get_name, ret)) { - return ret; - } - return "Unnamed"; + String ret = "Unnamed"; + GDVIRTUAL_CALL(_get_name, ret); + return ret; } -Array EditorSyntaxHighlighter::_get_supported_languages() const { - Array ret; - if (GDVIRTUAL_CALL(_get_supported_languages, ret)) { - return ret; - } - return Array(); +PackedStringArray EditorSyntaxHighlighter::_get_supported_languages() const { + PackedStringArray ret; + GDVIRTUAL_CALL(_get_supported_languages, ret); + return ret; } Ref<EditorSyntaxHighlighter> EditorSyntaxHighlighter::_create() const { @@ -117,20 +119,20 @@ void EditorStandardSyntaxHighlighter::_update_cache() { } /* Autoloads. */ - OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { - const ProjectSettings::AutoloadInfo &info = E.value(); + HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : autoloads) { + const ProjectSettings::AutoloadInfo &info = E.value; if (info.is_singleton) { highlighter->add_keyword_color(info.name, usertype_color); } } - const Ref<Script> script = _get_edited_resource(); - if (script.is_valid()) { + const Ref<Script> scr = _get_edited_resource(); + if (scr.is_valid()) { /* Core types. */ const Color basetype_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color"); List<String> core_types; - script->get_language()->get_core_type_words(&core_types); + scr->get_language()->get_core_type_words(&core_types); for (const String &E : core_types) { highlighter->add_keyword_color(E, basetype_color); } @@ -139,9 +141,9 @@ void EditorStandardSyntaxHighlighter::_update_cache() { const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); List<String> keywords; - script->get_language()->get_reserved_words(&keywords); + scr->get_language()->get_reserved_words(&keywords); for (const String &E : keywords) { - if (script->get_language()->is_control_flow_keyword(E)) { + if (scr->get_language()->is_control_flow_keyword(E)) { highlighter->add_keyword_color(E, control_flow_keyword_color); } else { highlighter->add_keyword_color(E, keyword_color); @@ -150,19 +152,19 @@ void EditorStandardSyntaxHighlighter::_update_cache() { /* Member types. */ const Color member_variable_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color"); - StringName instance_base = script->get_instance_base_type(); + StringName instance_base = scr->get_instance_base_type(); if (instance_base != StringName()) { List<PropertyInfo> plist; ClassDB::get_property_list(instance_base, &plist); for (const PropertyInfo &E : plist) { - String name = E.name; + String prop_name = E.name; if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) { continue; } - if (name.find("/") != -1) { + if (prop_name.contains("/")) { continue; } - highlighter->add_member_keyword_color(name, member_variable_color); + highlighter->add_member_keyword_color(prop_name, member_variable_color); } List<String> clist; @@ -175,7 +177,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() { /* Comments */ const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); List<String> comments; - script->get_language()->get_comment_delimiters(&comments); + scr->get_language()->get_comment_delimiters(&comments); for (const String &comment : comments) { String beg = comment.get_slice(" ", 0); String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String(); @@ -185,7 +187,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() { /* Strings */ const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); List<String> strings; - script->get_language()->get_string_delimiters(&strings); + scr->get_language()->get_string_delimiters(&strings); for (const String &string : strings) { String beg = string.get_slice(" ", 0); String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String(); @@ -208,6 +210,27 @@ Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const { return syntax_highlighter; } +//// + +void EditorJSONSyntaxHighlighter::_update_cache() { + highlighter->set_text_edit(text_edit); + highlighter->clear_keyword_colors(); + highlighter->clear_member_keyword_colors(); + highlighter->clear_color_regions(); + + highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color")); + highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color")); + + const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); + highlighter->add_color_region("\"", "\"", string_color); +} + +Ref<EditorSyntaxHighlighter> EditorJSONSyntaxHighlighter::_create() const { + Ref<EditorJSONSyntaxHighlighter> syntax_highlighter; + syntax_highlighter.instantiate(); + return syntax_highlighter; +} + //////////////////////////////////////////////////////////////////////////////// /*** SCRIPT EDITOR ****/ @@ -222,32 +245,32 @@ void ScriptEditorBase::_bind_methods() { ADD_SIGNAL(MethodInfo("request_open_script_at_line", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::INT, "line"))); ADD_SIGNAL(MethodInfo("request_save_history")); ADD_SIGNAL(MethodInfo("go_to_help", PropertyInfo(Variant::STRING, "what"))); - // TODO: This signal is no use for VisualScript. ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text"))); ADD_SIGNAL(MethodInfo("replace_in_files_requested", PropertyInfo(Variant::STRING, "text"))); + ADD_SIGNAL(MethodInfo("go_to_method", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::STRING, "method"))); } class EditorScriptCodeCompletionCache : public ScriptCodeCompletionCache { struct Cache { uint64_t time_loaded = 0; - RES cache; + Ref<Resource> cache; }; - Map<String, Cache> cached; + HashMap<String, Cache> cached; public: uint64_t max_time_cache = 5 * 60 * 1000; //minutes, five - int max_cache_size = 128; + uint32_t max_cache_size = 128; void cleanup() { - List<Map<String, Cache>::Element *> to_clean; + List<String> to_clean; - Map<String, Cache>::Element *I = cached.front(); + HashMap<String, Cache>::Iterator I = cached.begin(); while (I) { - if ((OS::get_singleton()->get_ticks_msec() - I->get().time_loaded) > max_time_cache) { - to_clean.push_back(I); + if ((OS::get_singleton()->get_ticks_msec() - I->value.time_loaded) > max_time_cache) { + to_clean.push_back(I->key); } - I = I->next(); + ++I; } while (to_clean.front()) { @@ -256,35 +279,35 @@ public: } } - virtual RES get_cached_resource(const String &p_path) { - Map<String, Cache>::Element *E = cached.find(p_path); + virtual Ref<Resource> get_cached_resource(const String &p_path) { + HashMap<String, Cache>::Iterator E = cached.find(p_path); if (!E) { Cache c; c.cache = ResourceLoader::load(p_path); E = cached.insert(p_path, c); } - E->get().time_loaded = OS::get_singleton()->get_ticks_msec(); + E->value.time_loaded = OS::get_singleton()->get_ticks_msec(); if (cached.size() > max_cache_size) { uint64_t older; - Map<String, Cache>::Element *O = cached.front(); - older = O->get().time_loaded; - Map<String, Cache>::Element *I = O; + HashMap<String, Cache>::Iterator O = cached.begin(); + older = O->value.time_loaded; + HashMap<String, Cache>::Iterator I = O; while (I) { - if (I->get().time_loaded < older) { - older = I->get().time_loaded; + if (I->value.time_loaded < older) { + older = I->value.time_loaded; O = I; } - I = I->next(); + ++I; } if (O != E) { //should never happen.. - cached.erase(O); + cached.remove(O); } } - return E->get().cache; + return E->value.cache; } virtual ~EditorScriptCodeCompletionCache() {} @@ -355,6 +378,7 @@ void ScriptEditorQuickOpen::_notification(int p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { search_box->set_right_icon(search_options->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); } break; + case NOTIFICATION_EXIT_TREE: { disconnect("confirmed", callable_mp(this, &ScriptEditorQuickOpen::_confirmed)); } break; @@ -374,7 +398,7 @@ ScriptEditorQuickOpen::ScriptEditorQuickOpen() { search_box->connect("gui_input", callable_mp(this, &ScriptEditorQuickOpen::_sbox_input)); search_options = memnew(Tree); vbc->add_margin_child(TTR("Matches:"), search_options, true); - get_ok_button()->set_text(TTR("Open")); + set_ok_button_text(TTR("Open")); get_ok_button()->set_disabled(true); register_text_enter(search_box); set_hide_on_ok(false); @@ -400,12 +424,12 @@ String ScriptEditor::_get_debug_tooltip(const String &p_text, Node *_se) { } void ScriptEditor::_breaked(bool p_breaked, bool p_can_debug) { - if (bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor"))) { + if (bool(EDITOR_GET("text_editor/external/use_external_editor"))) { return; } - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -415,7 +439,7 @@ void ScriptEditor::_breaked(bool p_breaked, bool p_can_debug) { } void ScriptEditor::_script_created(Ref<Script> p_script) { - editor->push_item(p_script.operator->()); + EditorNode::get_singleton()->push_item(p_script.operator->()); } void ScriptEditor::_goto_script_line2(int p_line) { @@ -425,11 +449,11 @@ void ScriptEditor::_goto_script_line2(int p_line) { } } -void ScriptEditor::_goto_script_line(REF p_script, int p_line) { - Ref<Script> script = Object::cast_to<Script>(*p_script); - if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) { +void ScriptEditor::_goto_script_line(Ref<RefCounted> p_script, int p_line) { + Ref<Script> scr = Object::cast_to<Script>(*p_script); + if (scr.is_valid() && (scr->has_source_code() || scr->get_path().is_resource_file())) { if (edit(p_script, p_line, 0)) { - editor->push_item(p_script.ptr()); + EditorNode::get_singleton()->push_item(p_script.ptr()); ScriptEditorBase *current = _get_current_editor(); if (ScriptTextEditor *script_text_editor = Object::cast_to<ScriptTextEditor>(current)) { @@ -437,56 +461,58 @@ void ScriptEditor::_goto_script_line(REF p_script, int p_line) { } else if (current) { current->goto_line(p_line, true); } + + _save_history(); } } } -void ScriptEditor::_set_execution(REF p_script, int p_line) { - Ref<Script> script = Object::cast_to<Script>(*p_script); - if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); +void ScriptEditor::_set_execution(Ref<RefCounted> p_script, int p_line) { + Ref<Script> scr = Object::cast_to<Script>(*p_script); + if (scr.is_valid() && (scr->has_source_code() || scr->get_path().is_resource_file())) { + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } - if ((script != nullptr && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == script->get_path()) { + if ((scr != nullptr && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) { se->set_executing_line(p_line); } } } } -void ScriptEditor::_clear_execution(REF p_script) { - Ref<Script> script = Object::cast_to<Script>(*p_script); - if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); +void ScriptEditor::_clear_execution(Ref<RefCounted> p_script) { + Ref<Script> scr = Object::cast_to<Script>(*p_script); + if (scr.is_valid() && (scr->has_source_code() || scr->get_path().is_resource_file())) { + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } - if ((script != nullptr && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == script->get_path()) { + if ((scr != nullptr && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) { se->clear_executing_line(); } } } } -void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) { - Ref<Script> script = Object::cast_to<Script>(*p_script); - if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) { +void ScriptEditor::_set_breakpoint(Ref<RefCounted> p_script, int p_line, bool p_enabled) { + Ref<Script> scr = Object::cast_to<Script>(*p_script); + if (scr.is_valid() && (scr->has_source_code() || scr->get_path().is_resource_file())) { // Update if open. - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); - if (se && se->get_edited_resource()->get_path() == script->get_path()) { + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); + if (se && se->get_edited_resource()->get_path() == scr->get_path()) { se->set_breakpoint(p_line, p_enabled); return; } } // Handle closed. - Dictionary state = script_editor_cache->get_value(script->get_path(), "state"); + Dictionary state = script_editor_cache->get_value(scr->get_path(), "state"); Array breakpoints; if (state.has("breakpoints")) { breakpoints = state["breakpoints"]; @@ -500,14 +526,14 @@ void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) { breakpoints.push_back(p_line); } state["breakpoints"] = breakpoints; - script_editor_cache->set_value(script->get_path(), "state", state); - EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), p_line + 1, false); + script_editor_cache->set_value(scr->get_path(), "state", state); + EditorDebuggerNode::get_singleton()->set_breakpoint(scr->get_path(), p_line + 1, false); } } void ScriptEditor::_clear_breakpoints() { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se) { se->clear_breakpoints(); } @@ -544,11 +570,11 @@ Array ScriptEditor::_get_cached_breakpoints_for_script(const String &p_path) con ScriptEditorBase *ScriptEditor::_get_current_editor() const { int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) { + if (selected < 0 || selected >= tab_container->get_tab_count()) { return nullptr; } - return Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); + return Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(selected)); } void ScriptEditor::_update_history_arrows() { @@ -561,7 +587,7 @@ void ScriptEditor::_save_history() { Node *n = tab_container->get_current_tab_control(); if (Object::cast_to<ScriptEditorBase>(n)) { - history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_edit_state(); + history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_navigation_state(); } if (Object::cast_to<EditorHelp>(n)) { history.write[history_pos].state = Object::cast_to<EditorHelp>(n)->get_scroll(); @@ -587,7 +613,7 @@ void ScriptEditor::_go_to_tab(int p_idx) { } } - Control *c = Object::cast_to<Control>(tab_container->get_child(p_idx)); + Control *c = tab_container->get_tab_control(p_idx); if (!c) { return; } @@ -596,7 +622,7 @@ void ScriptEditor::_go_to_tab(int p_idx) { Node *n = tab_container->get_current_tab_control(); if (Object::cast_to<ScriptEditorBase>(n)) { - history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_edit_state(); + history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_navigation_state(); } if (Object::cast_to<EditorHelp>(n)) { history.write[history_pos].state = Object::cast_to<EditorHelp>(n)->get_scroll(); @@ -622,9 +648,9 @@ void ScriptEditor::_go_to_tab(int p_idx) { Object::cast_to<ScriptEditorBase>(c)->ensure_focus(); } - Ref<Script> script = Object::cast_to<ScriptEditorBase>(c)->get_edited_resource(); - if (script != nullptr) { - notify_script_changed(script); + Ref<Script> scr = Object::cast_to<ScriptEditorBase>(c)->get_edited_resource(); + if (scr != nullptr) { + notify_script_changed(scr); } Object::cast_to<ScriptEditorBase>(c)->validate(); @@ -677,8 +703,9 @@ void ScriptEditor::_update_recent_scripts() { recent_scripts->add_separator(); recent_scripts->add_shortcut(ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Files"))); + recent_scripts->set_item_disabled(recent_scripts->get_item_id(recent_scripts->get_item_count() - 1), rc.is_empty()); - recent_scripts->set_as_minsize(); + recent_scripts->reset_size(); } void ScriptEditor::_open_recent_script(int p_idx) { @@ -697,11 +724,12 @@ void ScriptEditor::_open_recent_script(int p_idx) { if (FileAccess::exists(path)) { List<String> extensions; ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); + ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions); if (extensions.find(path.get_extension())) { - Ref<Script> script = ResourceLoader::load(path); - if (script.is_valid()) { - edit(script, true); + Ref<Resource> scr = ResourceLoader::load(path); + if (scr.is_valid()) { + edit(scr, true); return; } } @@ -713,7 +741,7 @@ void ScriptEditor::_open_recent_script(int p_idx) { return; } // if it's a path then it's most likely a deleted file not help - } else if (path.find("::") != -1) { + } else if (path.contains("::")) { // built-in script String res_path = path.get_slice("::", 0); if (ResourceLoader::get_resource_type(res_path) == "PackedScene") { @@ -723,9 +751,9 @@ void ScriptEditor::_open_recent_script(int p_idx) { } else { EditorNode::get_singleton()->load_resource(res_path); } - Ref<Script> script = ResourceLoader::load(path); - if (script.is_valid()) { - edit(script, true); + Ref<Script> scr = ResourceLoader::load(path); + if (scr.is_valid()) { + edit(scr, true); return; } } else if (!path.is_resource_file()) { @@ -746,19 +774,19 @@ void ScriptEditor::_show_error_dialog(String p_path) { void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { int selected = p_idx; - if (selected < 0 || selected >= tab_container->get_child_count()) { + if (selected < 0 || selected >= tab_container->get_tab_count()) { return; } - Node *tselected = tab_container->get_child(selected); + Node *tselected = tab_container->get_tab_control(selected); ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tselected); if (current) { - RES file = current->get_edited_resource(); + Ref<Resource> file = current->get_edited_resource(); if (p_save && file.is_valid()) { // Do not try to save internal scripts, but prompt to save in-memory // scripts which are not saved to disk yet (have empty path). - if (file->is_built_in()) { + if (!file->is_built_in()) { save_current_script(); } } @@ -768,9 +796,9 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { previous_scripts.push_back(file->get_path()); } - Ref<Script> script = file; - if (script.is_valid()) { - notify_script_close(script); + Ref<Script> scr = file; + if (scr.is_valid()) { + notify_script_close(scr); } } } @@ -801,14 +829,14 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { _save_editor_state(current); } memdelete(tselected); - if (idx >= tab_container->get_child_count()) { - idx = tab_container->get_child_count() - 1; + if (idx >= tab_container->get_tab_count()) { + idx = tab_container->get_tab_count() - 1; } if (idx >= 0) { if (history_pos >= 0) { - idx = history[history_pos].control->get_index(); + idx = tab_container->get_tab_idx_from_control(history[history_pos].control); } - tab_container->set_current_tab(idx); + _go_to_tab(idx); } else { _update_selected_editor_menu(); } @@ -832,9 +860,9 @@ void ScriptEditor::_close_discard_current_tab(const String &p_str) { } void ScriptEditor::_close_docs_tab() { - int child_count = tab_container->get_child_count(); + int child_count = tab_container->get_tab_count(); for (int i = child_count - 1; i >= 0; i--) { - EditorHelp *se = Object::cast_to<EditorHelp>(tab_container->get_child(i)); + EditorHelp *se = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i)); if (se) { _close_tab(i, true, false); @@ -845,14 +873,14 @@ void ScriptEditor::_close_docs_tab() { void ScriptEditor::_copy_script_path() { ScriptEditorBase *se = _get_current_editor(); if (se) { - RES script = se->get_edited_resource(); - DisplayServer::get_singleton()->clipboard_set(script->get_path()); + Ref<Resource> scr = se->get_edited_resource(); + DisplayServer::get_singleton()->clipboard_set(scr->get_path()); } } void ScriptEditor::_close_other_tabs() { int current_idx = tab_container->get_current_tab(); - for (int i = tab_container->get_child_count() - 1; i >= 0; i--) { + for (int i = tab_container->get_tab_count() - 1; i >= 0; i--) { if (i != current_idx) { script_close_queue.push_back(i); } @@ -861,7 +889,7 @@ void ScriptEditor::_close_other_tabs() { } void ScriptEditor::_close_all_tabs() { - for (int i = tab_container->get_child_count() - 1; i >= 0; i--) { + for (int i = tab_container->get_tab_count() - 1; i >= 0; i--) { script_close_queue.push_back(i); } _queue_close_tabs(); @@ -873,12 +901,12 @@ void ScriptEditor::_queue_close_tabs() { script_close_queue.pop_front(); tab_container->set_current_tab(idx); - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(idx)); + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(idx)); if (se) { // Maybe there are unsaved changes. if (se->is_unsaved()) { _ask_close_current_unsaved_tab(se); - erase_tab_confirm->connect(SceneStringNames::get_singleton()->visibility_changed, callable_mp(this, &ScriptEditor::_queue_close_tabs), varray(), CONNECT_ONESHOT); + erase_tab_confirm->connect(SceneStringNames::get_singleton()->visibility_changed, callable_mp(this, &ScriptEditor::_queue_close_tabs), CONNECT_ONE_SHOT); break; } } @@ -896,15 +924,15 @@ void ScriptEditor::_ask_close_current_unsaved_tab(ScriptEditorBase *current) { void ScriptEditor::_resave_scripts(const String &p_str) { apply_scripts(); - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } - RES script = se->get_edited_resource(); + Ref<Resource> scr = se->get_edited_resource(); - if (script->is_built_in()) { + if (scr->is_built_in()) { continue; //internal script, who cares } @@ -922,13 +950,13 @@ void ScriptEditor::_resave_scripts(const String &p_str) { } } - Ref<TextFile> text_file = script; + Ref<TextFile> text_file = scr; if (text_file != nullptr) { se->apply_code(); _save_text_file(text_file, text_file->get_path()); break; } else { - editor->save_resource(script); + EditorNode::get_singleton()->save_resource(scr); } se->tag_saved_version(); } @@ -936,60 +964,16 @@ void ScriptEditor::_resave_scripts(const String &p_str) { disk_changed->hide(); } -void ScriptEditor::_reload_scripts() { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); - if (!se) { - continue; - } - - RES edited_res = se->get_edited_resource(); - - if (edited_res->is_built_in()) { - continue; //internal script, who cares - } - - uint64_t last_date = edited_res->get_last_modified_time(); - uint64_t date = FileAccess::get_modified_time(edited_res->get_path()); - - if (last_date == date) { - continue; - } - - Ref<Script> script = edited_res; - if (script != nullptr) { - Ref<Script> rel_script = ResourceLoader::load(script->get_path(), script->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); - ERR_CONTINUE(!rel_script.is_valid()); - script->set_source_code(rel_script->get_source_code()); - script->set_last_modified_time(rel_script->get_last_modified_time()); - script->reload(); - } - - Ref<TextFile> text_file = edited_res; - if (text_file != nullptr) { - Error err; - Ref<TextFile> rel_text_file = _load_text_file(text_file->get_path(), &err); - ERR_CONTINUE(!rel_text_file.is_valid()); - text_file->set_text(rel_text_file->get_text()); - text_file->set_last_modified_time(rel_text_file->get_last_modified_time()); - } - se->reload_text(); - } - - disk_changed->hide(); - _update_script_names(); -} - void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } - RES script = se->get_edited_resource(); + Ref<Resource> scr = se->get_edited_resource(); - if (script == p_res) { + if (scr == p_res) { se->tag_saved_version(); } } @@ -1000,13 +984,13 @@ void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { void ScriptEditor::_scene_saved_callback(const String &p_path) { // If scene was saved, mark all built-in scripts from that scene as saved. - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } - RES edited_res = se->get_edited_resource(); + Ref<Resource> edited_res = se->get_edited_resource(); if (!edited_res->is_built_in()) { continue; // External script, who cares. @@ -1035,19 +1019,19 @@ void ScriptEditor::_live_auto_reload_running_scripts() { EditorDebuggerNode::get_singleton()->reload_scripts(); } -bool ScriptEditor::_test_script_times_on_disk(RES p_for_script) { +bool ScriptEditor::_test_script_times_on_disk(Ref<Resource> p_for_script) { disk_changed_list->clear(); TreeItem *r = disk_changed_list->create_item(); disk_changed_list->set_hide_root(true); bool need_ask = false; bool need_reload = false; - bool use_autoreload = bool(EDITOR_DEF("text_editor/behavior/files/auto_reload_scripts_on_external_change", false)); + bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change")); - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se) { - RES edited_res = se->get_edited_resource(); + Ref<Resource> edited_res = se->get_edited_resource(); if (p_for_script.is_valid() && edited_res.is_valid() && p_for_script != edited_res) { continue; } @@ -1073,7 +1057,7 @@ bool ScriptEditor::_test_script_times_on_disk(RES p_for_script) { if (need_reload) { if (!need_ask) { - script_editor->_reload_scripts(); + script_editor->reload_scripts(); need_reload = false; } else { disk_changed->call_deferred(SNAME("popup_centered_ratio"), 0.5); @@ -1087,13 +1071,13 @@ void ScriptEditor::_file_dialog_action(String p_file) { switch (file_dialog_option) { case FILE_NEW_TEXTFILE: { Error err; - FileAccess *file = FileAccess::open(p_file, FileAccess::WRITE, &err); - if (err) { - editor->show_warning(TTR("Error writing TextFile:") + "\n" + p_file, TTR("Error!")); - break; + { + Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err); + if (err) { + EditorNode::get_singleton()->show_warning(TTR("Error writing TextFile:") + "\n" + p_file, TTR("Error!")); + break; + } } - file->close(); - memdelete(file); if (EditorFileSystem::get_singleton()) { if (textfile_extensions.has(p_file.get_extension())) { @@ -1113,12 +1097,12 @@ void ScriptEditor::_file_dialog_action(String p_file) { case FILE_SAVE_AS: { ScriptEditorBase *current = _get_current_editor(); if (current) { - RES resource = current->get_edited_resource(); + Ref<Resource> resource = current->get_edited_resource(); String path = ProjectSettings::get_singleton()->localize_path(p_file); Error err = _save_text_file(resource, path); if (err != OK) { - editor->show_accept(TTR("Error saving file!"), TTR("OK")); + EditorNode::get_singleton()->show_accept(TTR("Error saving file!"), TTR("OK")); return; } @@ -1128,12 +1112,12 @@ void ScriptEditor::_file_dialog_action(String p_file) { } break; case THEME_SAVE_AS: { if (!EditorSettings::get_singleton()->save_text_editor_theme_as(p_file)) { - editor->show_warning(TTR("Error while saving theme."), TTR("Error Saving")); + EditorNode::get_singleton()->show_warning(TTR("Error while saving theme."), TTR("Error Saving")); } } break; case THEME_IMPORT: { if (!EditorSettings::get_singleton()->import_text_editor_theme(p_file)) { - editor->show_warning(TTR("Error importing theme."), TTR("Error Importing")); + EditorNode::get_singleton()->show_warning(TTR("Error importing theme."), TTR("Error Importing")); } } break; } @@ -1144,15 +1128,15 @@ Ref<Script> ScriptEditor::_get_current_script() { ScriptEditorBase *current = _get_current_editor(); if (current) { - Ref<Script> script = current->get_edited_resource(); - return script != nullptr ? script : nullptr; + Ref<Script> scr = current->get_edited_resource(); + return scr != nullptr ? scr : nullptr; } else { return nullptr; } } -Array ScriptEditor::_get_open_scripts() const { - Array ret; +TypedArray<Script> ScriptEditor::_get_open_scripts() const { + TypedArray<Script> ret; Vector<Ref<Script>> scripts = get_open_scripts(); int scrits_amount = scripts.size(); for (int idx_script = 0; idx_script < scrits_amount; idx_script++) { @@ -1163,6 +1147,7 @@ Array ScriptEditor::_get_open_scripts() const { bool ScriptEditor::toggle_scripts_panel() { list_split->set_visible(!list_split->is_visible()); + EditorSettings::get_singleton()->set_project_metadata("scripts_panel", "show_scripts_panel", list_split->is_visible()); return list_split->is_visible(); } @@ -1184,7 +1169,7 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog->clear_filters(); for (const String &E : textfile_extensions) { - file_dialog->add_filter("*." + E + " ; " + E.to_upper()); + file_dialog->add_filter("*." + E, E.to_upper()); } file_dialog->popup_file_dialog(); file_dialog->set_title(TTR("New Text File...")); @@ -1199,11 +1184,11 @@ void ScriptEditor::_menu_option(int p_option) { ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); file_dialog->clear_filters(); for (int i = 0; i < extensions.size(); i++) { - file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); + file_dialog->add_filter("*." + extensions[i], extensions[i].to_upper()); } for (const String &E : textfile_extensions) { - file_dialog->add_filter("*." + E + " ; " + E.to_upper()); + file_dialog->add_filter("*." + E, E.to_upper()); } file_dialog->popup_file_dialog(); @@ -1220,6 +1205,7 @@ void ScriptEditor::_menu_option(int p_option) { List<String> extensions; ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); + ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions); bool built_in = !path.is_resource_file(); if (extensions.find(path.get_extension()) || built_in) { @@ -1228,36 +1214,31 @@ void ScriptEditor::_menu_option(int p_option) { if (ResourceLoader::get_resource_type(res_path) == "PackedScene") { if (!EditorNode::get_singleton()->is_scene_open(res_path)) { EditorNode::get_singleton()->load_scene(res_path); - script_editor->call_deferred(SNAME("_menu_option"), p_option); - previous_scripts.push_back(path); //repeat the operation - return; } } else { EditorNode::get_singleton()->load_resource(res_path); } } - Ref<Script> scr = ResourceLoader::load(path); + Ref<Resource> scr = ResourceLoader::load(path); if (!scr.is_valid()) { - editor->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!")); + EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!")); file_dialog_option = -1; return; } edit(scr); file_dialog_option = -1; - return; } else { Error error; Ref<TextFile> text_file = _load_text_file(path, &error); if (error != OK) { - editor->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!")); + EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!")); } if (text_file.is_valid()) { edit(text_file); file_dialog_option = -1; - return; } } } break; @@ -1278,7 +1259,7 @@ void ScriptEditor::_menu_option(int p_option) { help_search_dialog->popup_dialog(); } break; case SEARCH_WEBSITE: { - OS::get_singleton()->shell_open("https://docs.godotengine.org/"); + OS::get_singleton()->shell_open(VERSION_DOCS_URL "/"); } break; case WINDOW_NEXT: { _history_forward(); @@ -1324,9 +1305,9 @@ void ScriptEditor::_menu_option(int p_option) { } } - RES resource = current->get_edited_resource(); + Ref<Resource> resource = current->get_edited_resource(); Ref<TextFile> text_file = resource; - Ref<Script> script = resource; + Ref<Script> scr = resource; if (text_file != nullptr) { file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); @@ -1343,32 +1324,29 @@ void ScriptEditor::_menu_option(int p_option) { break; } - if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); - for (int j = 0; j < documentations.size(); j++) { - const DocData::ClassDoc &doc = documentations.get(j); - if (EditorHelp::get_doc_data()->has_doc(doc.name)) { - EditorHelp::get_doc_data()->remove_doc(doc.name); - } - } + if (scr.is_valid()) { + clear_docs_from_script(scr); } - editor->push_item(resource.ptr()); - editor->save_resource_as(resource); + EditorNode::get_singleton()->push_item(resource.ptr()); + EditorNode::get_singleton()->save_resource_as(resource); - if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); - for (int j = 0; j < documentations.size(); j++) { - const DocData::ClassDoc &doc = documentations.get(j); - EditorHelp::get_doc_data()->add_doc(doc); - update_doc(doc.name); - } + if (scr.is_valid()) { + update_docs_from_script(scr); } } break; - case FILE_TOOL_RELOAD: case FILE_TOOL_RELOAD_SOFT: { - current->reload(p_option == FILE_TOOL_RELOAD_SOFT); + Ref<Script> scr = current->get_edited_resource(); + if (scr == nullptr || scr.is_null()) { + EditorNode::get_singleton()->show_warning(TTR("Can't obtain the script for reloading.")); + break; + } + if (!scr->is_tool()) { + EditorNode::get_singleton()->show_warning(TTR("Reload only takes effect on tool scripts.")); + return; + } + scr->reload(true); } break; case FILE_RUN: { @@ -1377,6 +1355,10 @@ void ScriptEditor::_menu_option(int p_option) { EditorNode::get_singleton()->show_warning(TTR("Can't obtain the script for running.")); break; } + if (!scr->is_tool()) { + EditorNode::get_singleton()->show_warning(TTR("Script is not in tool mode, will not be able to run.")); + return; + } current->apply_code(); Error err = scr->reload(false); //hard reload script before running always @@ -1385,10 +1367,6 @@ void ScriptEditor::_menu_option(int p_option) { EditorNode::get_singleton()->show_warning(TTR("Script failed reloading, check console for errors.")); return; } - if (!scr->is_tool()) { - EditorNode::get_singleton()->show_warning(TTR("Script is not in tool mode, will not be able to run.")); - return; - } if (!ClassDB::is_parent_class(scr->get_instance_base_type(), "EditorScript")) { EditorNode::get_singleton()->show_warning(TTR("To run this script, it must inherit EditorScript and be set to tool mode.")); @@ -1400,8 +1378,6 @@ void ScriptEditor::_menu_option(int p_option) { es->set_editor(EditorNode::get_singleton()); es->_run(); - - EditorNode::get_undo_redo()->clear_history(); } break; case FILE_CLOSE: { if (current->is_unsaved()) { @@ -1414,18 +1390,18 @@ void ScriptEditor::_menu_option(int p_option) { _copy_script_path(); } break; case SHOW_IN_FILE_SYSTEM: { - const RES script = current->get_edited_resource(); - String path = script->get_path(); + const Ref<Resource> scr = current->get_edited_resource(); + String path = scr->get_path(); if (!path.is_empty()) { - if (script->is_built_in()) { + if (scr->is_built_in()) { path = path.get_slice("::", 0); // Show the scene instead. } - FileSystemDock *file_system_dock = EditorNode::get_singleton()->get_filesystem_dock(); + FileSystemDock *file_system_dock = FileSystemDock::get_singleton(); file_system_dock->navigate_to_path(path); // Ensure that the FileSystem dock is visible. - TabContainer *tab_container = (TabContainer *)file_system_dock->get_parent_control(); - tab_container->set_current_tab(file_system_dock->get_index()); + TabContainer *dock_tab_container = (TabContainer *)file_system_dock->get_parent_control(); + dock_tab_container->set_current_tab(dock_tab_container->get_tab_idx_from_control(file_system_dock)); } } break; case CLOSE_DOCS: { @@ -1440,20 +1416,20 @@ void ScriptEditor::_menu_option(int p_option) { case WINDOW_MOVE_UP: { if (tab_container->get_current_tab() > 0) { tab_container->move_child(current, tab_container->get_current_tab() - 1); - tab_container->set_current_tab(tab_container->get_current_tab() - 1); + tab_container->set_current_tab(tab_container->get_current_tab()); _update_script_names(); } } break; case WINDOW_MOVE_DOWN: { - if (tab_container->get_current_tab() < tab_container->get_child_count() - 1) { + if (tab_container->get_current_tab() < tab_container->get_tab_count() - 1) { tab_container->move_child(current, tab_container->get_current_tab() + 1); - tab_container->set_current_tab(tab_container->get_current_tab() + 1); + tab_container->set_current_tab(tab_container->get_current_tab()); _update_script_names(); } } break; default: { if (p_option >= WINDOW_SELECT_BASE) { - tab_container->set_current_tab(p_option - WINDOW_SELECT_BASE); + _go_to_tab(p_option - WINDOW_SELECT_BASE); _update_script_names(); } } @@ -1486,14 +1462,14 @@ void ScriptEditor::_menu_option(int p_option) { case WINDOW_MOVE_UP: { if (tab_container->get_current_tab() > 0) { tab_container->move_child(help, tab_container->get_current_tab() - 1); - tab_container->set_current_tab(tab_container->get_current_tab() - 1); + tab_container->set_current_tab(tab_container->get_current_tab()); _update_script_names(); } } break; case WINDOW_MOVE_DOWN: { - if (tab_container->get_current_tab() < tab_container->get_child_count() - 1) { + if (tab_container->get_current_tab() < tab_container->get_tab_count() - 1) { tab_container->move_child(help, tab_container->get_current_tab() + 1); - tab_container->set_current_tab(tab_container->get_current_tab() + 1); + tab_container->set_current_tab(tab_container->get_current_tab()); _update_script_names(); } } break; @@ -1520,7 +1496,7 @@ void ScriptEditor::_theme_option(int p_option) { if (EditorSettings::get_singleton()->is_default_text_editor_theme()) { ScriptEditor::_show_save_theme_as_dialog(); } else if (!EditorSettings::get_singleton()->save_text_editor_theme()) { - editor->show_warning(TTR("Error while saving theme"), TTR("Error saving")); + EditorNode::get_singleton()->show_warning(TTR("Error while saving theme"), TTR("Error saving")); } } break; case THEME_SAVE_AS: { @@ -1535,11 +1511,56 @@ void ScriptEditor::_show_save_theme_as_dialog() { file_dialog_option = THEME_SAVE_AS; file_dialog->clear_filters(); file_dialog->add_filter("*.tet"); - file_dialog->set_current_path(EditorSettings::get_singleton()->get_text_editor_themes_dir().plus_file(EditorSettings::get_singleton()->get("text_editor/theme/color_theme"))); + file_dialog->set_current_path(EditorPaths::get_singleton()->get_text_editor_themes_dir().path_join(EDITOR_GET("text_editor/theme/color_theme"))); file_dialog->popup_file_dialog(); file_dialog->set_title(TTR("Save Theme As...")); } +bool ScriptEditor::_has_docs_tab() const { + const int child_count = tab_container->get_tab_count(); + for (int i = 0; i < child_count; i++) { + if (Object::cast_to<EditorHelp>(tab_container->get_tab_control(i))) { + return true; + } + } + return false; +} + +bool ScriptEditor::_has_script_tab() const { + const int child_count = tab_container->get_tab_count(); + for (int i = 0; i < child_count; i++) { + if (Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i))) { + return true; + } + } + return false; +} + +void ScriptEditor::_prepare_file_menu() { + PopupMenu *menu = file_menu->get_popup(); + const bool current_is_doc = _get_current_editor() == nullptr; + + menu->set_item_disabled(menu->get_item_index(FILE_REOPEN_CLOSED), previous_scripts.is_empty()); + + menu->set_item_disabled(menu->get_item_index(FILE_SAVE), current_is_doc); + menu->set_item_disabled(menu->get_item_index(FILE_SAVE_AS), current_is_doc); + menu->set_item_disabled(menu->get_item_index(FILE_SAVE_ALL), !_has_script_tab()); + + menu->set_item_disabled(menu->get_item_index(FILE_TOOL_RELOAD_SOFT), current_is_doc); + menu->set_item_disabled(menu->get_item_index(FILE_COPY_PATH), current_is_doc); + menu->set_item_disabled(menu->get_item_index(SHOW_IN_FILE_SYSTEM), current_is_doc); + + menu->set_item_disabled(menu->get_item_index(WINDOW_PREV), history_pos <= 0); + menu->set_item_disabled(menu->get_item_index(WINDOW_NEXT), history_pos >= history.size() - 1); + + menu->set_item_disabled(menu->get_item_index(FILE_CLOSE), tab_container->get_tab_count() < 1); + menu->set_item_disabled(menu->get_item_index(CLOSE_ALL), tab_container->get_tab_count() < 1); + menu->set_item_disabled(menu->get_item_index(CLOSE_OTHER_TABS), tab_container->get_tab_count() <= 1); + menu->set_item_disabled(menu->get_item_index(CLOSE_DOCS), !_has_docs_tab()); + + menu->set_item_disabled(menu->get_item_index(FILE_RUN), current_is_doc); +} + void ScriptEditor::_tab_changed(int p_which) { ensure_select_current(); } @@ -1547,17 +1568,18 @@ void ScriptEditor::_tab_changed(int p_which) { void ScriptEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - editor->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); - editor->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback)); - editor->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback)); - editor->connect("scene_saved", callable_mp(this, &ScriptEditor::_scene_saved_callback)); - editor->get_filesystem_dock()->connect("files_moved", callable_mp(this, &ScriptEditor::_files_moved)); - editor->get_filesystem_dock()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed)); + EditorNode::get_singleton()->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); + EditorNode::get_singleton()->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback)); + EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback)); + EditorNode::get_singleton()->connect("scene_saved", callable_mp(this, &ScriptEditor::_scene_saved_callback)); + FileSystemDock::get_singleton()->connect("files_moved", callable_mp(this, &ScriptEditor::_files_moved)); + FileSystemDock::get_singleton()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed)); script_list->connect("item_selected", callable_mp(this, &ScriptEditor::_script_selected)); members_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_members_overview_selected)); help_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_help_overview_selected)); - script_split->connect("dragged", callable_mp(this, &ScriptEditor::_script_split_dragged)); + script_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged)); + list_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged)); EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed)); EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed)); @@ -1568,7 +1590,7 @@ void ScriptEditor::_notification(int p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { help_search->set_icon(get_theme_icon(SNAME("HelpSearch"), SNAME("EditorIcons"))); - site_search->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); + site_search->set_icon(get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); if (is_layout_rtl()) { script_forward->set_icon(get_theme_icon(SNAME("Back"), SNAME("EditorIcons"))); @@ -1583,9 +1605,9 @@ void ScriptEditor::_notification(int p_what) { filter_scripts->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); filter_methods->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - filename->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))); + filename->add_theme_style_override("normal", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))); - recent_scripts->set_as_minsize(); + recent_scripts->reset_size(); if (is_inside_tree()) { _update_script_colors(); @@ -1595,15 +1617,15 @@ void ScriptEditor::_notification(int p_what) { case NOTIFICATION_READY: { get_tree()->connect("tree_changed", callable_mp(this, &ScriptEditor::_tree_changed)); - editor->get_inspector_dock()->connect("request_help", callable_mp(this, &ScriptEditor::_help_class_open)); - editor->connect("request_help_search", callable_mp(this, &ScriptEditor::_help_search)); + InspectorDock::get_singleton()->connect("request_help", callable_mp(this, &ScriptEditor::_help_class_open)); + EditorNode::get_singleton()->connect("request_help_search", callable_mp(this, &ScriptEditor::_help_search)); } break; case NOTIFICATION_EXIT_TREE: { - editor->disconnect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); + EditorNode::get_singleton()->disconnect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); } break; - case NOTIFICATION_WM_WINDOW_FOCUS_IN: { + case NOTIFICATION_APPLICATION_FOCUS_IN: { _test_script_times_on_disk(); _update_modified_scripts_for_external_editor(); } break; @@ -1613,15 +1635,12 @@ void ScriptEditor::_notification(int p_what) { find_in_files_button->show(); } else { if (find_in_files->is_visible_in_tree()) { - editor->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); } find_in_files_button->hide(); } } break; - - default: - break; } } @@ -1635,16 +1654,16 @@ bool ScriptEditor::can_take_away_focus() const { } void ScriptEditor::close_builtin_scripts_from_scene(const String &p_scene) { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se) { - Ref<Script> script = se->get_edited_resource(); - if (script == nullptr || !script.is_valid()) { + Ref<Script> scr = se->get_edited_resource(); + if (scr == nullptr || !scr.is_valid()) { continue; } - if (script->is_built_in() && script->get_path().begins_with(p_scene)) { //is an internal script and belongs to scene being closed + if (scr->is_built_in() && scr->get_path().begins_with(p_scene)) { //is an internal script and belongs to scene being closed _close_tab(i, false); i--; } @@ -1665,25 +1684,25 @@ void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) { } void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { - Set<String> loaded_scripts; - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + HashSet<String> loaded_scripts; + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } - Ref<Script> script = se->get_edited_resource(); - if (script == nullptr) { + Ref<Script> scr = se->get_edited_resource(); + if (scr == nullptr) { continue; } - String base = script->get_path(); + String base = scr->get_path(); loaded_scripts.insert(base); if (base.begins_with("local://") || base.is_empty()) { continue; } - Array bpoints = se->get_breakpoints(); + PackedInt32Array bpoints = se->get_breakpoints(); for (int j = 0; j < bpoints.size(); j++) { p_breakpoints->push_back(base + ":" + itos((int)bpoints[j] + 1)); } @@ -1719,7 +1738,7 @@ void ScriptEditor::_members_overview_selected(int p_idx) { } void ScriptEditor::_help_overview_selected(int p_idx) { - Node *current = tab_container->get_child(tab_container->get_current_tab()); + Node *current = tab_container->get_tab_control(tab_container->get_current_tab()); EditorHelp *se = Object::cast_to<EditorHelp>(current); if (!se) { return; @@ -1735,10 +1754,10 @@ void ScriptEditor::_script_selected(int p_idx) { } void ScriptEditor::ensure_select_current() { - if (tab_container->get_child_count() && tab_container->get_current_tab() >= 0) { + if (tab_container->get_tab_count() && tab_container->get_current_tab() >= 0) { ScriptEditorBase *se = _get_current_editor(); if (se) { - se->enable_editor(); + se->enable_editor(this); if (!grab_focus_block && is_visible_in_tree()) { se->ensure_focus(); @@ -1750,7 +1769,7 @@ void ScriptEditor::ensure_select_current() { _update_selected_editor_menu(); } -void ScriptEditor::_find_scripts(Node *p_base, Node *p_current, Set<Ref<Script>> &used) { +void ScriptEditor::_find_scripts(Node *p_base, Node *p_current, HashSet<Ref<Script>> &used) { if (p_current != p_base && p_current->get_owner() != p_base) { return; } @@ -1771,6 +1790,7 @@ struct _ScriptEditorItemData { String name; String sort_key; Ref<Texture2D> icon; + bool tool = false; int index = 0; String tooltip; bool used = false; @@ -1801,10 +1821,12 @@ void ScriptEditor::_update_members_overview_visibility() { if (members_overview_enabled && se->show_members_overview()) { members_overview_alphabeta_sort_button->set_visible(true); + filter_methods->set_visible(true); members_overview->set_visible(true); overview_vbox->set_visible(true); } else { members_overview_alphabeta_sort_button->set_visible(false); + filter_methods->set_visible(false); members_overview->set_visible(false); overview_vbox->set_visible(false); } @@ -1824,16 +1846,16 @@ void ScriptEditor::_update_members_overview() { } Vector<String> functions = se->get_functions(); - if (EditorSettings::get_singleton()->get("text_editor/script_list/sort_members_outline_alphabetically")) { + if (EDITOR_GET("text_editor/script_list/sort_members_outline_alphabetically")) { functions.sort(); } for (int i = 0; i < functions.size(); i++) { String filter = filter_methods->get_text(); String name = functions[i].get_slice(":", 0); - if (filter.is_empty() || filter.is_subsequence_ofi(name)) { + if (filter.is_empty() || filter.is_subsequence_ofn(name)) { members_overview->add_item(name); - members_overview->set_item_metadata(members_overview->get_item_count() - 1, functions[i].get_slice(":", 1).to_int() - 1); + members_overview->set_item_metadata(-1, functions[i].get_slice(":", 1).to_int() - 1); } } @@ -1845,12 +1867,12 @@ void ScriptEditor::_update_members_overview() { void ScriptEditor::_update_help_overview_visibility() { int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) { + if (selected < 0 || selected >= tab_container->get_tab_count()) { help_overview->set_visible(false); return; } - Node *current = tab_container->get_child(tab_container->get_current_tab()); + Node *current = tab_container->get_tab_control(tab_container->get_current_tab()); EditorHelp *se = Object::cast_to<EditorHelp>(current); if (!se) { help_overview->set_visible(false); @@ -1859,6 +1881,7 @@ void ScriptEditor::_update_help_overview_visibility() { if (help_overview_enabled) { members_overview_alphabeta_sort_button->set_visible(false); + filter_methods->set_visible(false); help_overview->set_visible(true); overview_vbox->set_visible(true); filename->set_text(se->get_name()); @@ -1872,11 +1895,11 @@ void ScriptEditor::_update_help_overview() { help_overview->clear(); int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) { + if (selected < 0 || selected >= tab_container->get_tab_count()) { return; } - Node *current = tab_container->get_child(tab_container->get_current_tab()); + Node *current = tab_container->get_tab_control(tab_container->get_current_tab()); EditorHelp *se = Object::cast_to<EditorHelp>(current); if (!se) { return; @@ -1890,15 +1913,16 @@ void ScriptEditor::_update_help_overview() { } void ScriptEditor::_update_script_colors() { - bool script_temperature_enabled = EditorSettings::get_singleton()->get("text_editor/script_list/script_temperature_enabled"); + bool script_temperature_enabled = EDITOR_GET("text_editor/script_list/script_temperature_enabled"); - int hist_size = EditorSettings::get_singleton()->get("text_editor/script_list/script_temperature_history_size"); + int hist_size = EDITOR_GET("text_editor/script_list/script_temperature_history_size"); Color hot_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + hot_color.set_s(hot_color.get_s() * 0.9); Color cold_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); for (int i = 0; i < script_list->get_item_count(); i++) { int c = script_list->get_item_metadata(i); - Node *n = tab_container->get_child(c); + Node *n = tab_container->get_tab_control(c); if (!n) { continue; } @@ -1906,11 +1930,11 @@ void ScriptEditor::_update_script_colors() { script_list->set_item_custom_bg_color(i, Color(0, 0, 0, 0)); if (script_temperature_enabled) { - if (!n->has_meta("__editor_pass")) { + int pass = n->get_meta("__editor_pass", -1); + if (pass < 0) { continue; } - int pass = n->get_meta("__editor_pass"); int h = edit_pass - pass; if (h > hist_size) { continue; @@ -1928,21 +1952,21 @@ void ScriptEditor::_update_script_names() { return; } - Set<Ref<Script>> used; + HashSet<Ref<Script>> used; Node *edited = EditorNode::get_singleton()->get_edited_scene(); if (edited) { _find_scripts(edited, edited, used); } script_list->clear(); - bool split_script_help = EditorSettings::get_singleton()->get("text_editor/script_list/group_help_pages"); - ScriptSortBy sort_by = (ScriptSortBy)(int)EditorSettings::get_singleton()->get("text_editor/script_list/sort_scripts_by"); - ScriptListName display_as = (ScriptListName)(int)EditorSettings::get_singleton()->get("text_editor/script_list/list_script_names_as"); + bool split_script_help = EDITOR_GET("text_editor/script_list/group_help_pages"); + ScriptSortBy sort_by = (ScriptSortBy)(int)EDITOR_GET("text_editor/script_list/sort_scripts_by"); + ScriptListName display_as = (ScriptListName)(int)EDITOR_GET("text_editor/script_list/list_script_names_as"); Vector<_ScriptEditorItemData> sedata; - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se) { Ref<Texture2D> icon = se->get_theme_icon(); String path = se->get_edited_resource()->get_path(); @@ -1953,6 +1977,7 @@ void ScriptEditor::_update_script_names() { se->set_meta("_edit_res_path", path); } String name = se->get_name(); + Ref<Script> scr = se->get_edited_resource(); _ScriptEditorItemData sd; sd.icon = icon; @@ -1962,6 +1987,9 @@ void ScriptEditor::_update_script_names() { sd.used = used.has(se->get_edited_resource()); sd.category = 0; sd.ref = se; + if (scr.is_valid()) { + sd.tool = scr->is_tool(); + } switch (sort_by) { case SORT_BY_NAME: { @@ -1981,7 +2009,7 @@ void ScriptEditor::_update_script_names() { } break; case DISPLAY_DIR_AND_NAME: { if (!path.get_base_dir().get_file().is_empty()) { - sd.name = path.get_base_dir().get_file().plus_file(name); + sd.name = path.get_base_dir().get_file().path_join(name); } else { sd.name = name; } @@ -2001,13 +2029,13 @@ void ScriptEditor::_update_script_names() { Vector<String> full_script_paths; for (int j = 0; j < sedata.size(); j++) { String name = sedata[j].name.replace("(*)", ""); - ScriptListName script_display = (ScriptListName)(int)EditorSettings::get_singleton()->get("text_editor/script_list/list_script_names_as"); + ScriptListName script_display = (ScriptListName)(int)EDITOR_GET("text_editor/script_list/list_script_names_as"); switch (script_display) { case DISPLAY_NAME: { name = name.get_file(); } break; case DISPLAY_DIR_AND_NAME: { - name = name.get_base_dir().get_file().plus_file(name.get_file()); + name = name.get_base_dir().get_file().path_join(name.get_file()); } break; default: break; @@ -2027,7 +2055,7 @@ void ScriptEditor::_update_script_names() { } } - EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); + EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i)); if (eh) { String name = eh->get_class(); Ref<Texture2D> icon = get_theme_icon(SNAME("Help"), SNAME("EditorIcons")); @@ -2068,21 +2096,27 @@ void ScriptEditor::_update_script_names() { sd.index = i; sedata.set(i, sd); } - tab_container->set_current_tab(new_prev_tab); - tab_container->set_current_tab(new_cur_tab); + _go_to_tab(new_prev_tab); + _go_to_tab(new_cur_tab); _sort_list_on_update = false; } Vector<_ScriptEditorItemData> sedata_filtered; for (int i = 0; i < sedata.size(); i++) { String filter = filter_scripts->get_text(); - if (filter.is_empty() || filter.is_subsequence_ofi(sedata[i].name)) { + if (filter.is_empty() || filter.is_subsequence_ofn(sedata[i].name)) { sedata_filtered.push_back(sedata[i]); } } + Color tool_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + tool_color.set_s(tool_color.get_s() * 1.5); for (int i = 0; i < sedata_filtered.size(); i++) { script_list->add_item(sedata_filtered[i].name, sedata_filtered[i].icon); + if (sedata_filtered[i].tool) { + script_list->set_item_icon_modulate(-1, tool_color); + } + int index = script_list->get_item_count() - 1; script_list->set_item_tooltip(index, sedata_filtered[i].tooltip); script_list->set_item_metadata(index, sedata_filtered[i].index); /* Saving as metadata the script's index in the tab container and not the filtered one */ @@ -2091,11 +2125,13 @@ void ScriptEditor::_update_script_names() { } if (tab_container->get_current_tab() == sedata_filtered[i].index) { script_list->select(index); + script_name_label->set_text(sedata_filtered[i].name); script_icon->set_texture(sedata_filtered[i].icon); + ScriptEditorBase *se = _get_current_editor(); if (se) { - se->enable_editor(); + se->enable_editor(this); _update_selected_editor_menu(); } } @@ -2110,18 +2146,6 @@ void ScriptEditor::_update_script_names() { _update_members_overview_visibility(); _update_help_overview_visibility(); _update_script_colors(); - - file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(FILE_REOPEN_CLOSED), previous_scripts.is_empty()); -} - -void ScriptEditor::_update_script_connections() { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(tab_container->get_child(i)); - if (!ste) { - continue; - } - ste->_update_connected_methods(); - } } Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) const { @@ -2136,7 +2160,7 @@ Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error Ref<TextFile> text_res(text_file); Error err = text_file->load_text(path); - ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load text file '" + path + "'."); + ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load text file '" + path + "'."); text_file->set_file_path(local_path); text_file->set_path(local_path, true); @@ -2159,45 +2183,46 @@ Error ScriptEditor::_save_text_file(Ref<TextFile> p_text_file, const String &p_p String source = sqscr->get_text(); Error err; - FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &err); + { + Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err); - ERR_FAIL_COND_V_MSG(err, err, "Cannot save text file '" + p_path + "'."); + ERR_FAIL_COND_V_MSG(err, err, "Cannot save text file '" + p_path + "'."); - file->store_string(source); - if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) { - memdelete(file); - return ERR_CANT_CREATE; + file->store_string(source); + if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) { + return ERR_CANT_CREATE; + } } - file->close(); - memdelete(file); if (ResourceSaver::get_timestamp_on_save()) { p_text_file->set_last_modified_time(FileAccess::get_modified_time(p_path)); } + EditorFileSystem::get_singleton()->update_file(p_path); + _res_saved_callback(sqscr); return OK; } -bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_grab_focus) { +bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col, bool p_grab_focus) { if (p_resource.is_null()) { return false; } - Ref<Script> script = p_resource; + Ref<Script> scr = p_resource; // Don't open dominant script if using an external editor. bool use_external_editor = - EditorSettings::get_singleton()->get("text_editor/external/use_external_editor") || - (script.is_valid() && script->get_language()->overrides_external_editor()); - use_external_editor = use_external_editor && !(script.is_valid() && script->is_built_in()); // Ignore external editor for built-in scripts. - const bool open_dominant = EditorSettings::get_singleton()->get("text_editor/behavior/files/open_dominant_script_on_scene_change"); + EDITOR_GET("text_editor/external/use_external_editor") || + (scr.is_valid() && scr->get_language()->overrides_external_editor()); + use_external_editor = use_external_editor && !(scr.is_valid() && scr->is_built_in()); // Ignore external editor for built-in scripts. + const bool open_dominant = EDITOR_GET("text_editor/behavior/files/open_dominant_script_on_scene_change"); const bool should_open = (open_dominant && !use_external_editor) || !EditorNode::get_singleton()->is_changing_scene(); - if (script.is_valid() && script->get_language()->overrides_external_editor()) { + if (scr.is_valid() && scr->get_language()->overrides_external_editor()) { if (should_open) { - Error err = script->get_language()->open_in_external_editor(script, p_line >= 0 ? p_line : 0, p_col); + Error err = scr->get_language()->open_in_external_editor(scr, p_line >= 0 ? p_line : 0, p_col); if (err != OK) { ERR_PRINT("Couldn't open script in the overridden external text editor"); } @@ -2207,10 +2232,9 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra if (use_external_editor && (EditorDebuggerNode::get_singleton()->get_dump_stack_script() != p_resource || EditorDebuggerNode::get_singleton()->get_debug_with_external_editor()) && - p_resource->get_path().is_resource_file() && - p_resource->get_class_name() != StringName("VisualScript")) { - String path = EditorSettings::get_singleton()->get("text_editor/external/exec_path"); - String flags = EditorSettings::get_singleton()->get("text_editor/external/exec_flags"); + p_resource->get_path().is_resource_file()) { + String path = EDITOR_GET("text_editor/external/exec_path"); + String flags = EDITOR_GET("text_editor/external/exec_flags"); List<String> args; bool has_file_flag = false; @@ -2236,7 +2260,7 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra } else if (flags[i] == '\0' || (!inside_quotes && flags[i] == ' ')) { String arg = flags.substr(from, num_chars); - if (arg.find("{file}") != -1) { + if (arg.contains("{file}")) { has_file_flag = true; } @@ -2258,22 +2282,25 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra args.push_back(script_path); } - Error err = OS::get_singleton()->create_process(path, args); - if (err == OK) { - return false; + if (!path.is_empty()) { + Error err = OS::get_singleton()->create_process(path, args); + if (err == OK) { + return false; + } } - WARN_PRINT("Couldn't open external text editor, using internal"); + + ERR_PRINT("Couldn't open external text editor, falling back to the internal editor. Review your `text_editor/external/` editor settings."); } - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } - if ((script != nullptr && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) { + if ((scr != nullptr && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) { if (should_open) { - se->enable_editor(); + se->enable_editor(this); if (tab_container->get_current_tab() != i) { _go_to_tab(i); @@ -2307,35 +2334,45 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra se->set_edited_resource(p_resource); - if (p_resource->get_class_name() != StringName("VisualScript")) { - bool highlighter_set = false; - for (int i = 0; i < syntax_highlighters.size(); i++) { - Ref<EditorSyntaxHighlighter> highlighter = syntax_highlighters[i]->_create(); - if (highlighter.is_null()) { - continue; - } - se->add_syntax_highlighter(highlighter); + // Syntax highlighting. + bool highlighter_set = false; + for (int i = 0; i < syntax_highlighters.size(); i++) { + Ref<EditorSyntaxHighlighter> highlighter = syntax_highlighters[i]->_create(); + if (highlighter.is_null()) { + continue; + } + se->add_syntax_highlighter(highlighter); - if (script != nullptr && !highlighter_set) { - Array languages = highlighter->_get_supported_languages(); - if (languages.find(script->get_language()->get_name()) > -1) { - se->set_syntax_highlighter(highlighter); - highlighter_set = true; - } + if (highlighter_set) { + continue; + } + + PackedStringArray languages = highlighter->_get_supported_languages(); + // If script try language, else use extension. + if (scr != nullptr) { + if (languages.has(scr->get_language()->get_name())) { + se->set_syntax_highlighter(highlighter); + highlighter_set = true; } + continue; + } + + if (languages.has(p_resource->get_path().get_extension())) { + se->set_syntax_highlighter(highlighter); + highlighter_set = true; } } tab_container->add_child(se); if (p_grab_focus) { - se->enable_editor(); + se->enable_editor(this); } // If we delete a script within the filesystem, the original resource path // is lost, so keep it as metadata to figure out the exact tab to delete. se->set_meta("_edit_res_path", p_resource->get_path()); - se->set_tooltip_request_func("_get_debug_tooltip", this); + se->set_tooltip_request_func(callable_mp(this, &ScriptEditor::_get_debug_tooltip)); if (se->get_edit_menu()) { se->get_edit_menu()->hide(); menu_hb->add_child(se->get_edit_menu()); @@ -2362,6 +2399,7 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra se->connect("request_save_history", callable_mp(this, &ScriptEditor::_save_history)); se->connect("search_in_files_requested", callable_mp(this, &ScriptEditor::_on_find_in_files_requested)); se->connect("replace_in_files_requested", callable_mp(this, &ScriptEditor::_on_replace_in_files_requested)); + se->connect("go_to_method", callable_mp(this, &ScriptEditor::script_goto_method)); //test for modification, maybe the script was not edited but was loaded @@ -2396,9 +2434,9 @@ void ScriptEditor::save_current_script() { } } - RES resource = current->get_edited_resource(); + Ref<Resource> resource = current->get_edited_resource(); Ref<TextFile> text_file = resource; - Ref<Script> script = resource; + Ref<Script> scr = resource; if (text_file != nullptr) { current->apply_code(); @@ -2406,14 +2444,8 @@ void ScriptEditor::save_current_script() { return; } - if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); - for (int j = 0; j < documentations.size(); j++) { - const DocData::ClassDoc &doc = documentations.get(j); - if (EditorHelp::get_doc_data()->has_doc(doc.name)) { - EditorHelp::get_doc_data()->remove_doc(doc.name); - } - } + if (scr.is_valid()) { + clear_docs_from_script(scr); } if (resource->is_built_in()) { @@ -2422,27 +2454,22 @@ void ScriptEditor::save_current_script() { if (!scene_path.is_empty()) { Vector<String> scene_to_save; scene_to_save.push_back(scene_path); - editor->save_scene_list(scene_to_save); + EditorNode::get_singleton()->save_scene_list(scene_to_save); } } else { - editor->save_resource(resource); + EditorNode::get_singleton()->save_resource(resource); } - if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); - for (int j = 0; j < documentations.size(); j++) { - const DocData::ClassDoc &doc = documentations.get(j); - EditorHelp::get_doc_data()->add_doc(doc); - update_doc(doc.name); - } + if (scr.is_valid()) { + update_docs_from_script(scr); } } void ScriptEditor::save_all_scripts() { Vector<String> scenes_to_save; - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -2465,60 +2492,48 @@ void ScriptEditor::save_all_scripts() { continue; } - RES edited_res = se->get_edited_resource(); + Ref<Resource> edited_res = se->get_edited_resource(); if (edited_res.is_valid()) { se->apply_code(); } if (!edited_res->is_built_in()) { Ref<TextFile> text_file = edited_res; - Ref<Script> script = edited_res; + Ref<Script> scr = edited_res; if (text_file != nullptr) { _save_text_file(text_file, text_file->get_path()); continue; } - if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); - for (int j = 0; j < documentations.size(); j++) { - const DocData::ClassDoc &doc = documentations.get(j); - if (EditorHelp::get_doc_data()->has_doc(doc.name)) { - EditorHelp::get_doc_data()->remove_doc(doc.name); - } - } + if (scr.is_valid()) { + clear_docs_from_script(scr); } - editor->save_resource(edited_res); //external script, save it + EditorNode::get_singleton()->save_resource(edited_res); //external script, save it - if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); - for (int j = 0; j < documentations.size(); j++) { - const DocData::ClassDoc &doc = documentations.get(j); - EditorHelp::get_doc_data()->add_doc(doc); - update_doc(doc.name); - } + if (scr.is_valid()) { + update_docs_from_script(scr); } } else { // For built-in scripts, save their scenes instead. const String scene_path = edited_res->get_path().get_slice("::", 0); - if (!scenes_to_save.has(scene_path)) { + if (!scene_path.is_empty() && !scenes_to_save.has(scene_path)) { scenes_to_save.push_back(scene_path); } } } if (!scenes_to_save.is_empty()) { - editor->save_scene_list(scenes_to_save); + EditorNode::get_singleton()->save_scene_list(scenes_to_save); } _update_script_names(); - EditorFileSystem::get_singleton()->update_script_classes(); } void ScriptEditor::apply_scripts() const { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -2526,26 +2541,78 @@ void ScriptEditor::apply_scripts() const { } } +void ScriptEditor::reload_scripts(bool p_refresh_only) { + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); + if (!se) { + continue; + } + + Ref<Resource> edited_res = se->get_edited_resource(); + + if (edited_res->is_built_in()) { + continue; //internal script, who cares + } + + if (!p_refresh_only) { + uint64_t last_date = edited_res->get_last_modified_time(); + uint64_t date = FileAccess::get_modified_time(edited_res->get_path()); + + if (last_date == date) { + continue; + } + + Ref<Script> scr = edited_res; + if (scr.is_valid()) { + Ref<Script> rel_scr = ResourceLoader::load(scr->get_path(), scr->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_CONTINUE(!rel_scr.is_valid()); + scr->set_source_code(rel_scr->get_source_code()); + scr->set_last_modified_time(rel_scr->get_last_modified_time()); + scr->reload(true); + } + + Ref<JSON> json = edited_res; + if (json != nullptr) { + Ref<JSON> rel_json = ResourceLoader::load(json->get_path(), json->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_CONTINUE(!rel_json.is_valid()); + json->parse(rel_json->get_parsed_text(), true); + json->set_last_modified_time(rel_json->get_last_modified_time()); + } + + Ref<TextFile> text_file = edited_res; + if (text_file.is_valid()) { + text_file->reload_from_file(); + } + } + + se->reload_text(); + } + + disk_changed->hide(); + _update_script_names(); +} + void ScriptEditor::open_script_create_dialog(const String &p_base_name, const String &p_base_path) { _menu_option(FILE_NEW); script_create_dialog->config(p_base_name, p_base_path); } void ScriptEditor::open_text_file_create_dialog(const String &p_base_path, const String &p_base_name) { - file_dialog->set_current_file(p_base_name); - file_dialog->set_current_dir(p_base_path); _menu_option(FILE_NEW_TEXTFILE); + file_dialog->set_current_dir(p_base_path); + file_dialog->set_current_file(p_base_name); open_textfile_after_create = false; } -RES ScriptEditor::open_file(const String &p_file) { +Ref<Resource> ScriptEditor::open_file(const String &p_file) { List<String> extensions; ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); + ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions); if (extensions.find(p_file.get_extension())) { - Ref<Script> scr = ResourceLoader::load(p_file); + Ref<Resource> scr = ResourceLoader::load(p_file); if (!scr.is_valid()) { - editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); - return RES(); + EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); + return Ref<Resource>(); } edit(scr); @@ -2555,20 +2622,20 @@ RES ScriptEditor::open_file(const String &p_file) { Error error; Ref<TextFile> text_file = _load_text_file(p_file, &error); if (error != OK) { - editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); - return RES(); + EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); + return Ref<Resource>(); } if (text_file.is_valid()) { edit(text_file); return text_file; } - return RES(); + return Ref<Resource>(); } void ScriptEditor::_editor_stop() { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -2579,17 +2646,17 @@ void ScriptEditor::_editor_stop() { void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const PackedStringArray &p_args) { ERR_FAIL_COND(!p_obj); - Ref<Script> script = p_obj->get_script(); - ERR_FAIL_COND(!script.is_valid()); + Ref<Script> scr = p_obj->get_script(); + ERR_FAIL_COND(!scr.is_valid()); - editor->push_item(script.ptr()); + EditorNode::get_singleton()->push_item(scr.ptr()); - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } - if (se->get_edited_resource() != script) { + if (se->get_edited_resource() != scr) { continue; } @@ -2600,7 +2667,7 @@ void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const script_list->select(script_list->find_metadata(i)); // Save the current script so the changes can be picked up by an external editor. - if (!script.ptr()->is_built_in()) { // But only if it's not built-in script. + if (!scr.ptr()->is_built_in()) { // But only if it's not built-in script. save_current_script(); } @@ -2627,36 +2694,36 @@ void ScriptEditor::_save_layout() { return; } - editor->save_layout(); + EditorNode::get_singleton()->save_layout(); } void ScriptEditor::_editor_settings_changed() { textfile_extensions.clear(); - const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); + const Vector<String> textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false); for (const String &E : textfile_ext) { textfile_extensions.insert(E); } - trim_trailing_whitespace_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/trim_trailing_whitespace_on_save"); - convert_indent_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/convert_indent_on_save"); - use_space_indentation = EditorSettings::get_singleton()->get("text_editor/behavior/indent/type"); + trim_trailing_whitespace_on_save = EDITOR_GET("text_editor/behavior/files/trim_trailing_whitespace_on_save"); + convert_indent_on_save = EDITOR_GET("text_editor/behavior/files/convert_indent_on_save"); + use_space_indentation = EDITOR_GET("text_editor/behavior/indent/type"); - members_overview_enabled = EditorSettings::get_singleton()->get("text_editor/script_list/show_members_overview"); - help_overview_enabled = EditorSettings::get_singleton()->get("text_editor/help/show_help_index"); + members_overview_enabled = EDITOR_GET("text_editor/script_list/show_members_overview"); + help_overview_enabled = EDITOR_GET("text_editor/help/show_help_index"); _update_members_overview_visibility(); _update_help_overview_visibility(); _update_autosave_timer(); if (current_theme.is_empty()) { - current_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); - } else if (current_theme != String(EditorSettings::get_singleton()->get("text_editor/theme/color_theme"))) { - current_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); + current_theme = EDITOR_GET("text_editor/theme/color_theme"); + } else if (current_theme != String(EDITOR_GET("text_editor/theme/color_theme"))) { + current_theme = EDITOR_GET("text_editor/theme/color_theme"); EditorSettings::get_singleton()->load_text_editor_theme(); } - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -2666,7 +2733,7 @@ void ScriptEditor::_editor_settings_changed() { _update_script_colors(); _update_script_names(); - ScriptServer::set_reload_scripts_on_save(EDITOR_DEF("text_editor/behavior/files/auto_reload_and_parse_scripts_on_save", true)); + ScriptServer::set_reload_scripts_on_save(EDITOR_GET("text_editor/behavior/files/auto_reload_and_parse_scripts_on_save")); } void ScriptEditor::_filesystem_changed() { @@ -2694,8 +2761,8 @@ void ScriptEditor::_files_moved(const String &p_old_file, const String &p_new_fi } void ScriptEditor::_file_removed(const String &p_removed_file) { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -2734,7 +2801,7 @@ void ScriptEditor::_update_autosave_timer() { return; } - float autosave_time = EditorSettings::get_singleton()->get("text_editor/behavior/files/autosave_interval_secs"); + float autosave_time = EDITOR_GET("text_editor/behavior/files/autosave_interval_secs"); if (autosave_time > 0) { autosave_timer->set_wait_time(autosave_time); autosave_timer->start(); @@ -2750,19 +2817,18 @@ void ScriptEditor::_tree_changed() { waiting_update_names = true; call_deferred(SNAME("_update_script_names")); - call_deferred(SNAME("_update_script_connections")); } -void ScriptEditor::_script_split_dragged(float) { +void ScriptEditor::_split_dragged(float) { _save_layout(); } Variant ScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { - if (tab_container->get_child_count() == 0) { + if (tab_container->get_tab_count() == 0) { return Variant(); } - Node *cur_node = tab_container->get_child(tab_container->get_current_tab()); + Node *cur_node = tab_container->get_tab_control(tab_container->get_current_tab()); HBoxContainer *drag_preview = memnew(HBoxContainer); String preview_name = ""; @@ -2782,6 +2848,7 @@ Variant ScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { if (!preview_icon.is_null()) { TextureRect *tf = memnew(TextureRect); tf->set_texture(preview_icon); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); drag_preview->add_child(tf); } Label *label = memnew(Label(preview_name)); @@ -2802,7 +2869,7 @@ bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data } if (String(d["type"]) == "script_list_element") { - Node *node = d["script_list_element"]; + Node *node = Object::cast_to<Node>(d["script_list_element"]); ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node); if (se) { @@ -2843,8 +2910,8 @@ bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data if (file.is_empty() || !FileAccess::exists(file)) { continue; } - if (ResourceLoader::exists(file, "Script")) { - Ref<Script> scr = ResourceLoader::load(file); + if (ResourceLoader::exists(file, "Script") || ResourceLoader::exists(file, "JSON")) { + Ref<Resource> scr = ResourceLoader::load(file); if (scr.is_valid()) { return true; } @@ -2875,7 +2942,7 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co } if (String(d["type"]) == "script_list_element") { - Node *node = d["script_list_element"]; + Node *node = Object::cast_to<Node>(d["script_list_element"]); ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node); EditorHelp *eh = Object::cast_to<EditorHelp>(node); @@ -2917,28 +2984,31 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co if (script_list->get_item_count() > 0) { new_index = script_list->get_item_metadata(script_list->get_item_at_position(p_point)); } - int num_tabs_before = tab_container->get_child_count(); + int num_tabs_before = tab_container->get_tab_count(); for (int i = 0; i < files.size(); i++) { String file = files[i]; if (file.is_empty() || !FileAccess::exists(file)) { continue; } - if (!ResourceLoader::exists(file, "Script") && !textfile_extensions.has(file.get_extension())) { + if (!ResourceLoader::exists(file, "Script") && !ResourceLoader::exists(file, "JSON") && !textfile_extensions.has(file.get_extension())) { continue; } - RES res = open_file(file); + Ref<Resource> res = open_file(file); if (res.is_valid()) { - if (tab_container->get_child_count() > num_tabs_before) { - tab_container->move_child(tab_container->get_child(tab_container->get_child_count() - 1), new_index); - num_tabs_before = tab_container->get_child_count(); - } else { /* Maybe script was already open */ - tab_container->move_child(tab_container->get_child(tab_container->get_current_tab()), new_index); + const int num_tabs = tab_container->get_tab_count(); + if (num_tabs > num_tabs_before) { + tab_container->move_child(tab_container->get_tab_control(tab_container->get_tab_count() - 1), new_index); + num_tabs_before = num_tabs; + } else if (num_tabs > 0) { /* Maybe script was already open */ + tab_container->move_child(tab_container->get_tab_control(tab_container->get_current_tab()), new_index); } } } - tab_container->set_current_tab(new_index); + if (tab_container->get_tab_count() > 0) { + tab_container->set_current_tab(new_index); + } _update_script_names(); } } @@ -2966,7 +3036,7 @@ void ScriptEditor::input(const Ref<InputEvent> &p_event) { } } -void ScriptEditor::unhandled_key_input(const Ref<InputEvent> &p_event) { +void ScriptEditor::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!is_visible_in_tree() || !p_event->is_pressed() || p_event->is_echo()) { @@ -2979,6 +3049,7 @@ void ScriptEditor::unhandled_key_input(const Ref<InputEvent> &p_event) { _go_to_tab(script_list->get_item_metadata(next_tab)); _update_script_names(); } + accept_event(); } if (ED_IS_SHORTCUT("script_editor/prev_script", p_event)) { if (script_list->get_item_count() > 1) { @@ -2987,35 +3058,27 @@ void ScriptEditor::unhandled_key_input(const Ref<InputEvent> &p_event) { _go_to_tab(script_list->get_item_metadata(next_tab)); _update_script_names(); } + accept_event(); } if (ED_IS_SHORTCUT("script_editor/window_move_up", p_event)) { _menu_option(WINDOW_MOVE_UP); + accept_event(); } if (ED_IS_SHORTCUT("script_editor/window_move_down", p_event)) { _menu_option(WINDOW_MOVE_DOWN); + accept_event(); } } -void ScriptEditor::_script_list_gui_input(const Ref<InputEvent> &ev) { - Ref<InputEventMouseButton> mb = ev; - if (mb.is_valid() && mb->is_pressed()) { - switch (mb->get_button_index()) { - case MouseButton::MIDDLE: { - // Right-click selects automatically; middle-click does not. - int idx = script_list->get_item_at_position(mb->get_position(), true); - if (idx >= 0) { - script_list->select(idx); - _script_selected(idx); - _menu_option(FILE_CLOSE); - } - } break; +void ScriptEditor::_script_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index) { + if (p_mouse_button_index == MouseButton::MIDDLE) { + script_list->select(p_item); + _script_selected(p_item); + _menu_option(FILE_CLOSE); + } - case MouseButton::RIGHT: { - _make_script_list_context_menu(); - } break; - default: - break; - } + if (p_mouse_button_index == MouseButton::RIGHT) { + _make_script_list_context_menu(); } } @@ -3023,11 +3086,11 @@ void ScriptEditor::_make_script_list_context_menu() { context_menu->clear(); int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) { + if (selected < 0 || selected >= tab_container->get_tab_count()) { return; } - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(selected)); if (se) { context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/save"), FILE_SAVE); context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/save_as"), FILE_SAVE_AS); @@ -3039,8 +3102,8 @@ void ScriptEditor::_make_script_list_context_menu() { if (se) { Ref<Script> scr = se->get_edited_resource(); if (scr != nullptr) { - context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/reload_script_soft"), FILE_TOOL_RELOAD_SOFT); if (!scr.is_null() && scr->is_tool()) { + context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/reload_script_soft"), FILE_TOOL_RELOAD_SOFT); context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/run_file"), FILE_RUN); context_menu->add_separator(); } @@ -3055,13 +3118,19 @@ void ScriptEditor::_make_script_list_context_menu() { context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/window_sort"), WINDOW_SORT); context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/toggle_scripts_panel"), TOGGLE_SCRIPTS_PANEL); + context_menu->set_item_disabled(context_menu->get_item_index(CLOSE_ALL), tab_container->get_tab_count() <= 0); + context_menu->set_item_disabled(context_menu->get_item_index(CLOSE_OTHER_TABS), tab_container->get_tab_count() <= 1); + context_menu->set_item_disabled(context_menu->get_item_index(WINDOW_MOVE_UP), tab_container->get_current_tab() <= 0); + context_menu->set_item_disabled(context_menu->get_item_index(WINDOW_MOVE_DOWN), tab_container->get_current_tab() >= tab_container->get_tab_count() - 1); + context_menu->set_item_disabled(context_menu->get_item_index(WINDOW_SORT), tab_container->get_tab_count() <= 1); + context_menu->set_position(get_screen_position() + get_local_mouse_position()); context_menu->reset_size(); context_menu->popup(); } void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { - if (!bool(EDITOR_DEF("text_editor/behavior/files/restore_scripts_on_load", true))) { + if (!bool(EDITOR_GET("text_editor/behavior/files/restore_scripts_on_load"))) { return; } @@ -3077,9 +3146,10 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { restoring_layout = true; - Set<String> loaded_scripts; + HashSet<String> loaded_scripts; List<String> extensions; ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); + ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions); for (int i = 0; i < scripts.size(); i++) { String path = scripts[i]; @@ -3098,7 +3168,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { loaded_scripts.insert(path); if (extensions.find(path.get_extension())) { - Ref<Script> scr = ResourceLoader::load(path); + Ref<Resource> scr = ResourceLoader::load(path); if (!scr.is_valid()) { continue; } @@ -3117,7 +3187,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { } if (!script_info.is_empty()) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(tab_container->get_tab_count() - 1)); + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(tab_container->get_tab_count() - 1)); if (se) { se->set_edit_state(script_info["state"]); } @@ -3132,12 +3202,16 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { _help_class_open(path); } - for (int i = 0; i < tab_container->get_child_count(); i++) { - tab_container->get_child(i)->set_meta("__editor_pass", Variant()); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + tab_container->get_tab_control(i)->set_meta("__editor_pass", Variant()); + } + + if (p_layout->has_section_key("ScriptEditor", "script_split_offset")) { + script_split->set_split_offset(p_layout->get_value("ScriptEditor", "script_split_offset")); } - if (p_layout->has_section_key("ScriptEditor", "split_offset")) { - script_split->set_split_offset(p_layout->get_value("ScriptEditor", "split_offset")); + if (p_layout->has_section_key("ScriptEditor", "list_split_offset")) { + list_split->set_split_offset(p_layout->get_value("ScriptEditor", "list_split_offset")); } // Remove any deleted editors that have been removed between launches. @@ -3169,8 +3243,8 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { Array scripts; Array helps; - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se) { String path = se->get_edited_resource()->get_path(); if (!path.is_resource_file()) { @@ -3181,7 +3255,7 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { scripts.push_back(path); } - EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); + EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i)); if (eh) { helps.push_back(eh->get_class()); @@ -3190,10 +3264,11 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { p_layout->set_value("ScriptEditor", "open_scripts", scripts); p_layout->set_value("ScriptEditor", "open_help", helps); - p_layout->set_value("ScriptEditor", "split_offset", script_split->get_split_offset()); + p_layout->set_value("ScriptEditor", "script_split_offset", script_split->get_split_offset()); + p_layout->set_value("ScriptEditor", "list_split_offset", list_split->get_split_offset()); // Save the cache. - script_editor_cache->save(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("script_editor_cache.cfg")); + script_editor_cache->save(EditorPaths::get_singleton()->get_project_settings_dir().path_join("script_editor_cache.cfg")); } void ScriptEditor::_help_class_open(const String &p_class) { @@ -3201,8 +3276,8 @@ void ScriptEditor::_help_class_open(const String &p_class) { return; } - for (int i = 0; i < tab_container->get_child_count(); i++) { - EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i)); if (eh && eh->get_class() == p_class) { _go_to_tab(i); @@ -3227,15 +3302,8 @@ void ScriptEditor::_help_class_open(const String &p_class) { void ScriptEditor::_help_class_goto(const String &p_desc) { String cname = p_desc.get_slice(":", 1); - for (int i = 0; i < tab_container->get_child_count(); i++) { - EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); - - if (eh && eh->get_class() == cname) { - _go_to_tab(i); - eh->go_to_help(p_desc); - _update_script_names(); - return; - } + if (_help_tab_goto(cname, p_desc)) { + return; } EditorHelp *eh = memnew(EditorHelp); @@ -3249,13 +3317,29 @@ void ScriptEditor::_help_class_goto(const String &p_desc) { _sort_list_on_update = true; _update_script_names(); _save_layout(); + + call_deferred("_help_tab_goto", cname, p_desc); +} + +bool ScriptEditor::_help_tab_goto(const String &p_name, const String &p_desc) { + for (int i = 0; i < tab_container->get_tab_count(); i++) { + EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i)); + + if (eh && eh->get_class() == p_name) { + _go_to_tab(i); + eh->go_to_help(p_desc); + _update_script_names(); + return true; + } + } + return false; } void ScriptEditor::update_doc(const String &p_name) { ERR_FAIL_COND(!EditorHelp::get_doc_data()->has_doc(p_name)); - for (int i = 0; i < tab_container->get_child_count(); i++) { - EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i)); if (eh && eh->get_class() == p_name) { eh->update_doc(); return; @@ -3263,11 +3347,34 @@ void ScriptEditor::update_doc(const String &p_name) { } } +void ScriptEditor::clear_docs_from_script(const Ref<Script> &p_script) { + ERR_FAIL_COND(p_script.is_null()); + + Vector<DocData::ClassDoc> documentations = p_script->get_documentation(); + for (int j = 0; j < documentations.size(); j++) { + const DocData::ClassDoc &doc = documentations.get(j); + if (EditorHelp::get_doc_data()->has_doc(doc.name)) { + EditorHelp::get_doc_data()->remove_doc(doc.name); + } + } +} + +void ScriptEditor::update_docs_from_script(const Ref<Script> &p_script) { + ERR_FAIL_COND(p_script.is_null()); + + Vector<DocData::ClassDoc> documentations = p_script->get_documentation(); + for (int j = 0; j < documentations.size(); j++) { + const DocData::ClassDoc &doc = documentations.get(j); + EditorHelp::get_doc_data()->add_doc(doc); + update_doc(doc.name); + } +} + void ScriptEditor::_update_selected_editor_menu() { - for (int i = 0; i < tab_container->get_child_count(); i++) { + for (int i = 0; i < tab_container->get_tab_count(); i++) { bool current = tab_container->get_current_tab() == i; - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se && se->get_edit_menu()) { if (current) { se->get_edit_menu()->show(); @@ -3280,15 +3387,17 @@ void ScriptEditor::_update_selected_editor_menu() { EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_current_tab_control()); script_search_menu->get_popup()->clear(); if (eh) { - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find", TTR("Find..."), KeyModifierMask::CMD | Key::F), HELP_SEARCH_FIND); + script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find", TTR("Find..."), KeyModifierMask::CMD_OR_CTRL | Key::F), HELP_SEARCH_FIND); script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_next", TTR("Find Next"), Key::F3), HELP_SEARCH_FIND_NEXT); script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_previous", TTR("Find Previous"), KeyModifierMask::SHIFT | Key::F3), HELP_SEARCH_FIND_PREVIOUS); script_search_menu->get_popup()->add_separator(); - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::F), SEARCH_IN_FILES); + script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F), SEARCH_IN_FILES); + script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/replace_in_files", TTR("Replace in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R), REPLACE_IN_FILES); script_search_menu->show(); } else { - if (tab_container->get_child_count() == 0) { - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::F), SEARCH_IN_FILES); + if (tab_container->get_tab_count() == 0) { + script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F), SEARCH_IN_FILES); + script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/replace_in_files", TTR("Replace in Files"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R), REPLACE_IN_FILES); script_search_menu->show(); } else { script_search_menu->hide(); @@ -3300,24 +3409,25 @@ void ScriptEditor::_update_history_pos(int p_new_pos) { Node *n = tab_container->get_current_tab_control(); if (Object::cast_to<ScriptEditorBase>(n)) { - history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_edit_state(); + history.write[history_pos].state = Object::cast_to<ScriptEditorBase>(n)->get_navigation_state(); } if (Object::cast_to<EditorHelp>(n)) { history.write[history_pos].state = Object::cast_to<EditorHelp>(n)->get_scroll(); } history_pos = p_new_pos; - tab_container->set_current_tab(history[history_pos].control->get_index()); + tab_container->set_current_tab(tab_container->get_tab_idx_from_control(history[history_pos].control)); n = history[history_pos].control; - if (Object::cast_to<ScriptEditorBase>(n)) { - Object::cast_to<ScriptEditorBase>(n)->set_edit_state(history[history_pos].state); - Object::cast_to<ScriptEditorBase>(n)->ensure_focus(); + ScriptEditorBase *seb = Object::cast_to<ScriptEditorBase>(n); + if (seb) { + seb->set_edit_state(history[history_pos].state); + seb->ensure_focus(); - Ref<Script> script = Object::cast_to<ScriptEditorBase>(n)->get_edited_resource(); - if (script != nullptr) { - notify_script_changed(script); + Ref<Script> scr = seb->get_edited_resource(); + if (scr != nullptr) { + notify_script_changed(scr); } } @@ -3347,25 +3457,25 @@ void ScriptEditor::_history_back() { Vector<Ref<Script>> ScriptEditor::get_open_scripts() const { Vector<Ref<Script>> out_scripts = Vector<Ref<Script>>(); - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } - Ref<Script> script = se->get_edited_resource(); - if (script != nullptr) { - out_scripts.push_back(script); + Ref<Script> scr = se->get_edited_resource(); + if (scr != nullptr) { + out_scripts.push_back(scr); } } return out_scripts; } -Array ScriptEditor::_get_open_script_editors() const { - Array script_editors; - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); +TypedArray<ScriptEditorBase> ScriptEditor::_get_open_script_editors() const { + TypedArray<ScriptEditorBase> script_editors; + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -3377,10 +3487,10 @@ Array ScriptEditor::_get_open_script_editors() const { void ScriptEditor::set_scene_root_script(Ref<Script> p_script) { // Don't open dominant script if using an external editor. bool use_external_editor = - EditorSettings::get_singleton()->get("text_editor/external/use_external_editor") || + EDITOR_GET("text_editor/external/use_external_editor") || (p_script.is_valid() && p_script->get_language()->overrides_external_editor()); use_external_editor = use_external_editor && !(p_script.is_valid() && p_script->is_built_in()); // Ignore external editor for built-in scripts. - const bool open_dominant = EditorSettings::get_singleton()->get("text_editor/behavior/files/open_dominant_script_on_scene_change"); + const bool open_dominant = EDITOR_GET("text_editor/behavior/files/open_dominant_script_on_scene_change"); if (open_dominant && !use_external_editor && p_script.is_valid()) { edit(p_script); @@ -3406,9 +3516,15 @@ void ScriptEditor::_help_search(String p_text) { } void ScriptEditor::_open_script_request(const String &p_path) { - Ref<Script> script = ResourceLoader::load(p_path); - if (script.is_valid()) { - script_editor->edit(script, false); + Ref<Script> scr = ResourceLoader::load(p_path); + if (scr.is_valid()) { + script_editor->edit(scr, false); + return; + } + + Ref<JSON> json = ResourceLoader::load(p_path); + if (json.is_valid()) { + script_editor->edit(json, false); return; } @@ -3423,7 +3539,7 @@ void ScriptEditor::_open_script_request(const String &p_path) { void ScriptEditor::register_syntax_highlighter(const Ref<EditorSyntaxHighlighter> &p_syntax_highlighter) { ERR_FAIL_COND(p_syntax_highlighter.is_null()); - if (syntax_highlighters.find(p_syntax_highlighter) == -1) { + if (!syntax_highlighters.has(p_syntax_highlighter)) { syntax_highlighters.push_back(p_syntax_highlighter); } } @@ -3443,7 +3559,7 @@ void ScriptEditor::register_create_script_editor_function(CreateScriptEditorFunc } void ScriptEditor::_script_changed() { - NodeDock::singleton->update_lists(); + NodeDock::get_singleton()->update_lists(); } void ScriptEditor::_on_find_in_files_requested(String text) { @@ -3461,21 +3577,22 @@ void ScriptEditor::_on_replace_in_files_requested(String text) { void ScriptEditor::_on_find_in_files_result_selected(String fpath, int line_number, int begin, int end) { if (ResourceLoader::exists(fpath)) { - RES res = ResourceLoader::load(fpath); + Ref<Resource> res = ResourceLoader::load(fpath); if (fpath.get_extension() == "gdshader") { ShaderEditorPlugin *shader_editor = Object::cast_to<ShaderEditorPlugin>(EditorNode::get_singleton()->get_editor_data().get_editor("Shader")); shader_editor->edit(res.ptr()); shader_editor->make_visible(true); - shader_editor->get_shader_editor()->goto_line_selection(line_number - 1, begin, end); + shader_editor->get_shader_editor(res)->goto_line_selection(line_number - 1, begin, end); return; } else if (fpath.get_extension() == "tscn") { - editor->load_scene(fpath); + EditorNode::get_singleton()->load_scene(fpath); return; } else { - Ref<Script> script = res; - if (script.is_valid()) { - edit(script); + Ref<Script> scr = res; + Ref<JSON> json = res; + if (scr.is_valid() || json.is_valid()) { + edit(scr); ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(_get_current_editor()); if (ste) { @@ -3512,7 +3629,7 @@ void ScriptEditor::_start_find_in_files(bool with_replace) { find_in_files->set_replace_text(find_in_files_dialog->get_replace_text()); find_in_files->start_search(); - editor->make_bottom_panel_item_visible(find_in_files); + EditorNode::get_singleton()->make_bottom_panel_item_visible(find_in_files); } void ScriptEditor::_on_find_in_files_modified_files(PackedStringArray paths) { @@ -3535,9 +3652,8 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_goto_script_line2", &ScriptEditor::_goto_script_line2); ClassDB::bind_method("_copy_script_path", &ScriptEditor::_copy_script_path); - ClassDB::bind_method("_get_debug_tooltip", &ScriptEditor::_get_debug_tooltip); - ClassDB::bind_method("_update_script_connections", &ScriptEditor::_update_script_connections); ClassDB::bind_method("_help_class_open", &ScriptEditor::_help_class_open); + ClassDB::bind_method("_help_tab_goto", &ScriptEditor::_help_tab_goto); ClassDB::bind_method("_live_auto_reload_running_scripts", &ScriptEditor::_live_auto_reload_running_scripts); ClassDB::bind_method("_update_members_overview", &ScriptEditor::_update_members_overview); ClassDB::bind_method("_update_recent_scripts", &ScriptEditor::_update_recent_scripts); @@ -3548,10 +3664,6 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("register_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::register_syntax_highlighter); ClassDB::bind_method(D_METHOD("unregister_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::unregister_syntax_highlighter); - ClassDB::bind_method(D_METHOD("_get_drag_data_fw", "point", "from"), &ScriptEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("_can_drop_data_fw", "point", "data", "from"), &ScriptEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("_drop_data_fw", "point", "data", "from"), &ScriptEditor::drop_data_fw); - ClassDB::bind_method(D_METHOD("goto_line", "line_number"), &ScriptEditor::_goto_script_line2); ClassDB::bind_method(D_METHOD("get_current_script"), &ScriptEditor::_get_current_script); ClassDB::bind_method(D_METHOD("get_open_scripts"), &ScriptEditor::_get_open_scripts); @@ -3561,20 +3673,19 @@ void ScriptEditor::_bind_methods() { ADD_SIGNAL(MethodInfo("script_close", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script"))); } -ScriptEditor::ScriptEditor(EditorNode *p_editor) { +ScriptEditor::ScriptEditor() { current_theme = ""; script_editor_cache.instantiate(); - script_editor_cache->load(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("script_editor_cache.cfg")); + script_editor_cache->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("script_editor_cache.cfg")); completion_cache = memnew(EditorScriptCodeCompletionCache); restoring_layout = false; waiting_update_names = false; pending_auto_reload = false; auto_reload_running_scripts = true; - members_overview_enabled = EditorSettings::get_singleton()->get("text_editor/script_list/show_members_overview"); - help_overview_enabled = EditorSettings::get_singleton()->get("text_editor/help/show_help_index"); - editor = p_editor; + members_overview_enabled = EDITOR_GET("text_editor/script_list/show_members_overview"); + help_overview_enabled = EDITOR_GET("text_editor/help/show_help_index"); VBoxContainer *main_container = memnew(VBoxContainer); add_child(main_container); @@ -3595,7 +3706,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { list_split->add_child(scripts_vbox); filter_scripts = memnew(LineEdit); - filter_scripts->set_placeholder(TTR("Filter scripts")); + filter_scripts->set_placeholder(TTR("Filter Scripts")); filter_scripts->set_clear_button_enabled(true); filter_scripts->connect("text_changed", callable_mp(this, &ScriptEditor::_filter_scripts_text_changed)); scripts_vbox->add_child(filter_scripts); @@ -3606,9 +3717,9 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { script_list->set_v_size_flags(SIZE_EXPAND_FILL); script_split->set_split_offset(70 * EDSCALE); _sort_list_on_update = true; - script_list->connect("gui_input", callable_mp(this, &ScriptEditor::_script_list_gui_input), varray(), CONNECT_DEFERRED); + script_list->connect("item_clicked", callable_mp(this, &ScriptEditor::_script_list_clicked), CONNECT_DEFERRED); script_list->set_allow_rmb_select(true); - script_list->set_drag_forwarding(this); + SET_DRAG_FORWARDING_GCD(script_list, ScriptEditor); context_menu = memnew(PopupMenu); add_child(context_menu); @@ -3619,6 +3730,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { overview_vbox->set_v_size_flags(SIZE_EXPAND_FILL); list_split->add_child(overview_vbox); + list_split->set_visible(EditorSettings::get_singleton()->get_project_metadata("scripts_panel", "show_scripts_panel", true)); buttons_hbox = memnew(HBoxContainer); overview_vbox->add_child(buttons_hbox); @@ -3630,15 +3742,15 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { members_overview_alphabeta_sort_button = memnew(Button); members_overview_alphabeta_sort_button->set_flat(true); - members_overview_alphabeta_sort_button->set_tooltip(TTR("Toggle alphabetical sorting of the method list.")); + members_overview_alphabeta_sort_button->set_tooltip_text(TTR("Toggle alphabetical sorting of the method list.")); members_overview_alphabeta_sort_button->set_toggle_mode(true); - members_overview_alphabeta_sort_button->set_pressed(EditorSettings::get_singleton()->get("text_editor/script_list/sort_members_outline_alphabetically")); + members_overview_alphabeta_sort_button->set_pressed(EDITOR_GET("text_editor/script_list/sort_members_outline_alphabetically")); members_overview_alphabeta_sort_button->connect("toggled", callable_mp(this, &ScriptEditor::_toggle_members_overview_alpha_sort)); buttons_hbox->add_child(members_overview_alphabeta_sort_button); filter_methods = memnew(LineEdit); - filter_methods->set_placeholder(TTR("Filter methods")); + filter_methods->set_placeholder(TTR("Filter Methods")); filter_methods->set_clear_button_enabled(true); filter_methods->connect("text_changed", callable_mp(this, &ScriptEditor::_filter_methods_text_changed)); overview_vbox->add_child(filter_methods); @@ -3675,10 +3787,10 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { ED_SHORTCUT("script_editor/window_move_up", TTR("Move Up"), KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::UP); ED_SHORTCUT("script_editor/window_move_down", TTR("Move Down"), KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::DOWN); // FIXME: These should be `Key::GREATER` and `Key::LESS` but those don't work. - ED_SHORTCUT("script_editor/next_script", TTR("Next Script"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::PERIOD); - ED_SHORTCUT("script_editor/prev_script", TTR("Previous Script"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::COMMA); + ED_SHORTCUT("script_editor/next_script", TTR("Next Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::PERIOD); + ED_SHORTCUT("script_editor/prev_script", TTR("Previous Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::COMMA); set_process_input(true); - set_process_unhandled_input(true); + set_process_shortcut_input(true); file_menu = memnew(MenuButton); file_menu->set_text(TTR("File")); @@ -3686,10 +3798,10 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { file_menu->set_shortcut_context(this); menu_hb->add_child(file_menu); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new", TTR("New Script...")), FILE_NEW); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new_textfile", TTR("New Text File...")), FILE_NEW_TEXTFILE); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new", TTR("New Script..."), KeyModifierMask::CMD_OR_CTRL | Key::N), FILE_NEW); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new_textfile", TTR("New Text File..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::N), FILE_NEW_TEXTFILE); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/open", TTR("Open...")), FILE_OPEN); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::T), FILE_REOPEN_CLOSED); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::T), FILE_REOPEN_CLOSED); file_menu->get_popup()->add_submenu_item(TTR("Open Recent"), "RecentScripts", FILE_OPEN_RECENT); recent_scripts = memnew(PopupMenu); @@ -3699,11 +3811,11 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { _update_recent_scripts(); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save", TTR("Save"), KeyModifierMask::ALT | KeyModifierMask::CMD | Key::S), FILE_SAVE); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save", TTR("Save"), KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL | Key::S), FILE_SAVE); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_as", TTR("Save As...")), FILE_SAVE_AS); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_all", TTR("Save All"), KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::S), FILE_SAVE_ALL); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reload_script_soft", TTR("Soft Reload Script"), KeyModifierMask::CMD | KeyModifierMask::ALT | Key::R), FILE_TOOL_RELOAD_SOFT); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reload_script_soft", TTR("Soft Reload Tool Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::R), FILE_TOOL_RELOAD_SOFT); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/copy_path", TTR("Copy Script Path")), FILE_COPY_PATH); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/show_in_file_system", TTR("Show in FileSystem")), SHOW_IN_FILE_SYSTEM); file_menu->get_popup()->add_separator(); @@ -3726,17 +3838,18 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/save_theme_as", TTR("Save Theme As...")), THEME_SAVE_AS); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_file", TTR("Close"), KeyModifierMask::CMD | Key::W), FILE_CLOSE); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_file", TTR("Close"), KeyModifierMask::CMD_OR_CTRL | Key::W), FILE_CLOSE); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_all", TTR("Close All")), CLOSE_ALL); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_other_tabs", TTR("Close Other Tabs")), CLOSE_OTHER_TABS); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_docs", TTR("Close Docs")), CLOSE_DOCS); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/run_file", TTR("Run"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::X), FILE_RUN); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/run_file", TTR("Run"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::X), FILE_RUN); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/toggle_scripts_panel", TTR("Toggle Scripts Panel"), KeyModifierMask::CMD | Key::BACKSLASH), TOGGLE_SCRIPTS_PANEL); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/toggle_scripts_panel", TTR("Toggle Scripts Panel"), KeyModifierMask::CMD_OR_CTRL | Key::BACKSLASH), TOGGLE_SCRIPTS_PANEL); file_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptEditor::_menu_option)); + file_menu->get_popup()->connect("about_to_popup", callable_mp(this, &ScriptEditor::_prepare_file_menu)); script_search_menu = memnew(MenuButton); script_search_menu->set_text(TTR("Search")); @@ -3745,12 +3858,12 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { script_search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptEditor::_menu_option)); menu_hb->add_child(script_search_menu); - MenuButton *debug_menu = memnew(MenuButton); - menu_hb->add_child(debug_menu); - debug_menu->hide(); // Handled by EditorDebuggerNode below. + MenuButton *debug_menu_btn = memnew(MenuButton); + menu_hb->add_child(debug_menu_btn); + debug_menu_btn->hide(); // Handled by EditorDebuggerNode below. EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton(); - debugger->set_script_debug_button(debug_menu); + debugger->set_script_debug_button(debug_menu_btn); debugger->connect("goto_script_line", callable_mp(this, &ScriptEditor::_goto_script_line)); debugger->connect("set_execution", callable_mp(this, &ScriptEditor::_set_execution)); debugger->connect("clear_execution", callable_mp(this, &ScriptEditor::_clear_execution)); @@ -3773,16 +3886,16 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { site_search = memnew(Button); site_search->set_flat(true); site_search->set_text(TTR("Online Docs")); - site_search->connect("pressed", callable_mp(this, &ScriptEditor::_menu_option), varray(SEARCH_WEBSITE)); + site_search->connect("pressed", callable_mp(this, &ScriptEditor::_menu_option).bind(SEARCH_WEBSITE)); menu_hb->add_child(site_search); - site_search->set_tooltip(TTR("Open Godot online documentation.")); + site_search->set_tooltip_text(TTR("Open Godot online documentation.")); help_search = memnew(Button); help_search->set_flat(true); help_search->set_text(TTR("Search Help")); - help_search->connect("pressed", callable_mp(this, &ScriptEditor::_menu_option), varray(SEARCH_HELP)); + help_search->connect("pressed", callable_mp(this, &ScriptEditor::_menu_option).bind(SEARCH_HELP)); menu_hb->add_child(help_search); - help_search->set_tooltip(TTR("Search the reference documentation.")); + help_search->set_tooltip_text(TTR("Search the reference documentation.")); menu_hb->add_child(memnew(VSeparator)); @@ -3791,21 +3904,21 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { script_back->connect("pressed", callable_mp(this, &ScriptEditor::_history_back)); menu_hb->add_child(script_back); script_back->set_disabled(true); - script_back->set_tooltip(TTR("Go to previous edited document.")); + script_back->set_tooltip_text(TTR("Go to previous edited document.")); script_forward = memnew(Button); script_forward->set_flat(true); script_forward->connect("pressed", callable_mp(this, &ScriptEditor::_history_forward)); menu_hb->add_child(script_forward); script_forward->set_disabled(true); - script_forward->set_tooltip(TTR("Go to next edited document.")); + script_forward->set_tooltip_text(TTR("Go to next edited document.")); tab_container->connect("tab_changed", callable_mp(this, &ScriptEditor::_tab_changed)); erase_tab_confirm = memnew(ConfirmationDialog); - erase_tab_confirm->get_ok_button()->set_text(TTR("Save")); + erase_tab_confirm->set_ok_button_text(TTR("Save")); erase_tab_confirm->add_button(TTR("Discard"), DisplayServer::get_singleton()->get_swap_cancel_ok(), "discard"); - erase_tab_confirm->connect("confirmed", callable_mp(this, &ScriptEditor::_close_current_tab), varray(true)); + erase_tab_confirm->connect("confirmed", callable_mp(this, &ScriptEditor::_close_current_tab).bind(true)); erase_tab_confirm->connect("custom_action", callable_mp(this, &ScriptEditor::_close_discard_current_tab)); add_child(erase_tab_confirm); @@ -3835,8 +3948,8 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { vbc->add_child(disk_changed_list); disk_changed_list->set_v_size_flags(SIZE_EXPAND_FILL); - disk_changed->connect("confirmed", callable_mp(this, &ScriptEditor::_reload_scripts)); - disk_changed->get_ok_button()->set_text(TTR("Reload")); + disk_changed->connect("confirmed", callable_mp(this, &ScriptEditor::reload_scripts).bind(false)); + disk_changed->set_ok_button_text(TTR("Reload")); disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); disk_changed->connect("custom_action", callable_mp(this, &ScriptEditor::_resave_scripts)); @@ -3859,11 +3972,11 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { help_search_dialog->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto)); find_in_files_dialog = memnew(FindInFilesDialog); - find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_FIND_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files), varray(false)); - find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_REPLACE_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files), varray(true)); + find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_FIND_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files).bind(false)); + find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_REPLACE_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files).bind(true)); add_child(find_in_files_dialog); find_in_files = memnew(FindInFilesPanel); - find_in_files_button = editor->add_bottom_panel_item(TTR("Search Results"), find_in_files); + find_in_files_button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Search Results"), find_in_files); find_in_files->set_custom_minimum_size(Size2(0, 200) * EDSCALE); find_in_files->connect(FindInFilesPanel::SIGNAL_RESULT_SELECTED, callable_mp(this, &ScriptEditor::_on_find_in_files_result_selected)); find_in_files->connect(FindInFilesPanel::SIGNAL_FILES_MODIFIED, callable_mp(this, &ScriptEditor::_on_find_in_files_modified_files)); @@ -3873,14 +3986,18 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { history_pos = -1; edit_pass = 0; - trim_trailing_whitespace_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/trim_trailing_whitespace_on_save"); - convert_indent_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/convert_indent_on_save"); - use_space_indentation = EditorSettings::get_singleton()->get("text_editor/behavior/indent/type"); + trim_trailing_whitespace_on_save = EDITOR_GET("text_editor/behavior/files/trim_trailing_whitespace_on_save"); + convert_indent_on_save = EDITOR_GET("text_editor/behavior/files/convert_indent_on_save"); + use_space_indentation = EDITOR_GET("text_editor/behavior/indent/type"); ScriptServer::edit_request_func = _open_script_request; - add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles"))); - tab_container->add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditor"), SNAME("EditorStyles"))); + add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles"))); + tab_container->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditor"), SNAME("EditorStyles"))); + + Ref<EditorJSONSyntaxHighlighter> json_syntax_highlighter; + json_syntax_highlighter.instantiate(); + register_syntax_highlighter(json_syntax_highlighter); } ScriptEditor::~ScriptEditor() { @@ -3892,7 +4009,7 @@ void ScriptEditorPlugin::edit(Object *p_object) { Script *p_script = Object::cast_to<Script>(p_object); String res_path = p_script->get_path().get_slice("::", 0); - if (p_script->is_built_in()) { + if (p_script->is_built_in() && !res_path.is_empty()) { if (ResourceLoader::get_resource_type(res_path) == "PackedScene") { if (!EditorNode::get_singleton()->is_scene_open(res_path)) { EditorNode::get_singleton()->load_scene(res_path); @@ -3902,6 +4019,8 @@ void ScriptEditorPlugin::edit(Object *p_object) { } } script_editor->edit(p_script); + } else if (Object::cast_to<JSON>(p_object)) { + script_editor->edit(Object::cast_to<JSON>(p_object)); } else if (Object::cast_to<TextFile>(p_object)) { script_editor->edit(Object::cast_to<TextFile>(p_object)); } @@ -3916,6 +4035,10 @@ bool ScriptEditorPlugin::handles(Object *p_object) const { return true; } + if (Object::cast_to<JSON>(p_object)) { + return true; + } + return p_object->is_class("Script"); } @@ -3942,12 +4065,6 @@ void ScriptEditorPlugin::apply_changes() { script_editor->apply_scripts(); } -void ScriptEditorPlugin::restore_global_state() { -} - -void ScriptEditorPlugin::save_global_state() { -} - void ScriptEditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) { script_editor->set_window_layout(p_layout); } @@ -3964,15 +4081,14 @@ void ScriptEditorPlugin::edited_scene_changed() { script_editor->edited_scene_changed(); } -ScriptEditorPlugin::ScriptEditorPlugin(EditorNode *p_node) { - editor = p_node; - script_editor = memnew(ScriptEditor(p_node)); - editor->get_main_control()->add_child(script_editor); +ScriptEditorPlugin::ScriptEditorPlugin() { + script_editor = memnew(ScriptEditor); + EditorNode::get_singleton()->get_main_screen_control()->add_child(script_editor); script_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); script_editor->hide(); - EDITOR_DEF("text_editor/behavior/files/auto_reload_scripts_on_external_change", true); + EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change"); ScriptServer::set_reload_scripts_on_save(EDITOR_DEF("text_editor/behavior/files/auto_reload_and_parse_scripts_on_save", true)); EDITOR_DEF("text_editor/behavior/files/open_dominant_script_on_scene_change", true); EDITOR_DEF("text_editor/external/use_external_editor", false); @@ -3988,7 +4104,7 @@ ScriptEditorPlugin::ScriptEditorPlugin(EditorNode *p_node) { EDITOR_DEF("text_editor/external/exec_flags", "{file}"); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "text_editor/external/exec_flags", PROPERTY_HINT_PLACEHOLDER_TEXT, "Call flags with placeholders: {project}, {file}, {col}, {line}.")); - ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::T); + ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::T); ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Scripts")); } diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index ca409e15ca..f8e684ae34 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -1,70 +1,71 @@ -/*************************************************************************/ -/* script_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* script_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 SCRIPT_EDITOR_PLUGIN_H #define SCRIPT_EDITOR_PLUGIN_H -#include "core/object/script_language.h" -#include "editor/code_editor.h" -#include "editor/editor_help.h" -#include "editor/editor_help_search.h" #include "editor/editor_plugin.h" -#include "editor/script_create_dialog.h" -#include "scene/gui/item_list.h" -#include "scene/gui/line_edit.h" -#include "scene/gui/menu_button.h" -#include "scene/gui/split_container.h" -#include "scene/gui/tab_container.h" -#include "scene/gui/text_edit.h" -#include "scene/gui/tree.h" -#include "scene/main/timer.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/panel_container.h" +#include "scene/resources/syntax_highlighter.h" #include "scene/resources/text_file.h" +class EditorFileDialog; +class EditorHelpSearch; +class FindReplaceBar; +class HSplitContainer; +class ItemList; +class MenuButton; +class TabContainer; +class TextureRect; +class Tree; +class VSplitContainer; + class EditorSyntaxHighlighter : public SyntaxHighlighter { GDCLASS(EditorSyntaxHighlighter, SyntaxHighlighter) private: - REF edited_resourse; + Ref<RefCounted> edited_resourse; protected: static void _bind_methods(); GDVIRTUAL0RC(String, _get_name) - GDVIRTUAL0RC(Array, _get_supported_languages) + GDVIRTUAL0RC(PackedStringArray, _get_supported_languages) public: virtual String _get_name() const; - virtual Array _get_supported_languages() const; + virtual PackedStringArray _get_supported_languages() const; - void _set_edited_resource(const RES &p_res) { edited_resourse = p_res; } - REF _get_edited_resource() { return edited_resourse; } + void _set_edited_resource(const Ref<Resource> &p_res) { edited_resourse = p_res; } + Ref<RefCounted> _get_edited_resource() { return edited_resourse; } virtual Ref<EditorSyntaxHighlighter> _create() const; }; @@ -95,13 +96,31 @@ public: virtual Ref<EditorSyntaxHighlighter> _create() const override; }; +class EditorJSONSyntaxHighlighter : public EditorSyntaxHighlighter { + GDCLASS(EditorJSONSyntaxHighlighter, EditorSyntaxHighlighter) + +private: + Ref<CodeHighlighter> highlighter; + +public: + virtual void _update_cache() override; + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override { return highlighter->get_line_syntax_highlighting(p_line); } + + virtual PackedStringArray _get_supported_languages() const override { return PackedStringArray{ "json" }; } + virtual String _get_name() const override { return TTR("JSON"); } + + virtual Ref<EditorSyntaxHighlighter> _create() const override; + + EditorJSONSyntaxHighlighter() { highlighter.instantiate(); } +}; + /////////////////////////////////////////////////////////////////////////////// class ScriptEditorQuickOpen : public ConfirmationDialog { GDCLASS(ScriptEditorQuickOpen, ConfirmationDialog); - LineEdit *search_box; - Tree *search_options; + LineEdit *search_box = nullptr; + Tree *search_options = nullptr; String function; void _update_search(); @@ -134,16 +153,17 @@ public: virtual void set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) = 0; virtual void apply_code() = 0; - virtual RES get_edited_resource() const = 0; + virtual Ref<Resource> get_edited_resource() const = 0; virtual Vector<String> get_functions() = 0; - virtual void set_edited_resource(const RES &p_res) = 0; - virtual void enable_editor() = 0; + virtual void set_edited_resource(const Ref<Resource> &p_res) = 0; + virtual void enable_editor(Control *p_shortcut_context = nullptr) = 0; virtual void reload_text() = 0; virtual String get_name() = 0; virtual Ref<Texture2D> get_theme_icon() = 0; virtual bool is_unsaved() = 0; virtual Variant get_edit_state() = 0; virtual void set_edit_state(const Variant &p_state) = 0; + virtual Variant get_navigation_state() = 0; virtual void goto_line(int p_line, bool p_with_error = false) = 0; virtual void set_executing_line(int p_line) = 0; virtual void clear_executing_line() = 0; @@ -154,7 +174,7 @@ public: virtual void ensure_focus() = 0; virtual void tag_saved_version() = 0; virtual void reload(bool p_soft) {} - virtual Array get_breakpoints() = 0; + virtual PackedInt32Array get_breakpoints() = 0; virtual void set_breakpoint(int p_line, bool p_enabled) = 0; virtual void clear_breakpoints() = 0; virtual void add_callback(const String &p_function, PackedStringArray p_args) = 0; @@ -165,7 +185,7 @@ public: virtual bool show_members_overview() = 0; - virtual void set_tooltip_request_func(String p_method, Object *p_obj) = 0; + virtual void set_tooltip_request_func(const Callable &p_toolip_callback) = 0; virtual Control *get_edit_menu() = 0; virtual void clear_edit_menu() = 0; virtual void set_find_replace_bar(FindReplaceBar *p_bar) = 0; @@ -177,7 +197,7 @@ public: ScriptEditorBase() {} }; -typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const RES &p_resource); +typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const Ref<Resource> &p_resource); class EditorScriptCodeCompletionCache; class FindInFilesDialog; @@ -186,7 +206,6 @@ class FindInFilesPanel; class ScriptEditor : public PanelContainer { GDCLASS(ScriptEditor, PanelContainer); - EditorNode *editor; enum { FILE_NEW, FILE_NEW_TEXTFILE, @@ -205,7 +224,6 @@ class ScriptEditor : public PanelContainer { TOGGLE_SCRIPTS_PANEL, SHOW_IN_FILE_SYSTEM, FILE_COPY_PATH, - FILE_TOOL_RELOAD, FILE_TOOL_RELOAD_SOFT, SEARCH_IN_FILES, REPLACE_IN_FILES, @@ -241,55 +259,55 @@ class ScriptEditor : public PanelContainer { DISPLAY_FULL_PATH, }; - HBoxContainer *menu_hb; - MenuButton *file_menu; - MenuButton *edit_menu; - MenuButton *script_search_menu; - MenuButton *debug_menu; - PopupMenu *context_menu; - Timer *autosave_timer; - uint64_t idle; - - PopupMenu *recent_scripts; - PopupMenu *theme_submenu; - - Button *help_search; - Button *site_search; - EditorHelpSearch *help_search_dialog; - - ItemList *script_list; - HSplitContainer *script_split; - ItemList *members_overview; - LineEdit *filter_scripts; - LineEdit *filter_methods; - VBoxContainer *scripts_vbox; - VBoxContainer *overview_vbox; - HBoxContainer *buttons_hbox; - Label *filename; - Button *members_overview_alphabeta_sort_button; + HBoxContainer *menu_hb = nullptr; + MenuButton *file_menu = nullptr; + MenuButton *edit_menu = nullptr; + MenuButton *script_search_menu = nullptr; + MenuButton *debug_menu = nullptr; + PopupMenu *context_menu = nullptr; + Timer *autosave_timer = nullptr; + uint64_t idle = 0; + + PopupMenu *recent_scripts = nullptr; + PopupMenu *theme_submenu = nullptr; + + Button *help_search = nullptr; + Button *site_search = nullptr; + EditorHelpSearch *help_search_dialog = nullptr; + + ItemList *script_list = nullptr; + HSplitContainer *script_split = nullptr; + ItemList *members_overview = nullptr; + LineEdit *filter_scripts = nullptr; + LineEdit *filter_methods = nullptr; + VBoxContainer *scripts_vbox = nullptr; + VBoxContainer *overview_vbox = nullptr; + HBoxContainer *buttons_hbox = nullptr; + Label *filename = nullptr; + Button *members_overview_alphabeta_sort_button = nullptr; bool members_overview_enabled; - ItemList *help_overview; + ItemList *help_overview = nullptr; bool help_overview_enabled; - VSplitContainer *list_split; - TabContainer *tab_container; - EditorFileDialog *file_dialog; - AcceptDialog *error_dialog; - ConfirmationDialog *erase_tab_confirm; - ScriptCreateDialog *script_create_dialog; - Button *scripts_visible; - FindReplaceBar *find_replace_bar; + VSplitContainer *list_split = nullptr; + TabContainer *tab_container = nullptr; + EditorFileDialog *file_dialog = nullptr; + AcceptDialog *error_dialog = nullptr; + ConfirmationDialog *erase_tab_confirm = nullptr; + ScriptCreateDialog *script_create_dialog = nullptr; + Button *scripts_visible = nullptr; + FindReplaceBar *find_replace_bar = nullptr; String current_theme; - TextureRect *script_icon; - Label *script_name_label; + TextureRect *script_icon = nullptr; + Label *script_name_label = nullptr; - Button *script_back; - Button *script_forward; + Button *script_back = nullptr; + Button *script_forward = nullptr; - FindInFilesDialog *find_in_files_dialog; - FindInFilesPanel *find_in_files; - Button *find_in_files_button; + FindInFilesDialog *find_in_files_dialog = nullptr; + FindInFilesPanel *find_in_files = nullptr; + Button *find_in_files_button = nullptr; enum { SCRIPT_EDITOR_FUNC_MAX = 32, @@ -315,18 +333,20 @@ class ScriptEditor : public PanelContainer { void _menu_option(int p_option); void _theme_option(int p_option); void _show_save_theme_as_dialog(); + bool _has_docs_tab() const; + bool _has_script_tab() const; + void _prepare_file_menu(); - Tree *disk_changed_list; - ConfirmationDialog *disk_changed; + Tree *disk_changed_list = nullptr; + ConfirmationDialog *disk_changed = nullptr; bool restoring_layout; String _get_debug_tooltip(const String &p_text, Node *_se); void _resave_scripts(const String &p_str); - void _reload_scripts(); - bool _test_script_times_on_disk(RES p_for_script = Ref<Resource>()); + bool _test_script_times_on_disk(Ref<Resource> p_for_script = Ref<Resource>()); void _add_recent_script(String p_path); void _update_recent_scripts(); @@ -357,7 +377,7 @@ class ScriptEditor : public PanelContainer { void _update_selected_editor_menu(); - EditorScriptCodeCompletionCache *completion_cache; + EditorScriptCodeCompletionCache *completion_cache = nullptr; void _editor_stop(); @@ -373,17 +393,17 @@ class ScriptEditor : public PanelContainer { bool convert_indent_on_save; void _goto_script_line2(int p_line); - void _goto_script_line(REF p_script, int p_line); - void _set_execution(REF p_script, int p_line); - void _clear_execution(REF p_script); + void _goto_script_line(Ref<RefCounted> p_script, int p_line); + void _set_execution(Ref<RefCounted> p_script, int p_line); + void _clear_execution(Ref<RefCounted> p_script); void _breaked(bool p_breaked, bool p_can_debug); void _script_created(Ref<Script> p_script); - void _set_breakpoint(REF p_scrpt, int p_line, bool p_enabled); + void _set_breakpoint(Ref<RefCounted> p_scrpt, int p_line, bool p_enabled); void _clear_breakpoints(); Array _get_cached_breakpoints_for_script(const String &p_path) const; ScriptEditorBase *_get_current_editor() const; - Array _get_open_script_editors() const; + TypedArray<ScriptEditorBase> _get_open_script_editors() const; Ref<ConfigFile> script_editor_cache; void _save_editor_state(ScriptEditorBase *p_editor); @@ -401,7 +421,6 @@ class ScriptEditor : public PanelContainer { void _filter_scripts_text_changed(const String &p_newtext); void _filter_methods_text_changed(const String &p_newtext); void _update_script_names(); - void _update_script_connections(); bool _sort_list_on_update; void _members_overview_selected(int p_idx); @@ -411,20 +430,20 @@ class ScriptEditor : public PanelContainer { void _update_help_overview(); void _help_overview_selected(int p_idx); - void _find_scripts(Node *p_base, Node *p_current, Set<Ref<Script>> &used); + void _find_scripts(Node *p_base, Node *p_current, HashSet<Ref<Script>> &used); void _tree_changed(); - void _script_split_dragged(float); + void _split_dragged(float); Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); virtual void input(const Ref<InputEvent> &p_event) override; - virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; + virtual void shortcut_input(const Ref<InputEvent> &p_event) override; - void _script_list_gui_input(const Ref<InputEvent> &ev); + void _script_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index); void _make_script_list_context_menu(); void _help_search(String p_text); @@ -436,6 +455,7 @@ class ScriptEditor : public PanelContainer { void _help_class_open(const String &p_class); void _help_class_goto(const String &p_desc); + bool _help_tab_goto(const String &p_name, const String &p_desc); void _update_history_arrows(); void _save_history(); void _go_to_tab(int p_idx); @@ -448,9 +468,9 @@ class ScriptEditor : public PanelContainer { void _file_dialog_action(String p_file); Ref<Script> _get_current_script(); - Array _get_open_scripts() const; + TypedArray<Script> _get_open_scripts() const; - Set<String> textfile_extensions; + HashSet<String> textfile_extensions; Ref<TextFile> _load_text_file(const String &p_path, Error *r_error) const; Error _save_text_file(Ref<TextFile> p_text_file, const String &p_path); @@ -474,14 +494,15 @@ public: bool toggle_scripts_panel(); bool is_scripts_panel_toggled(); void apply_scripts() const; + void reload_scripts(bool p_refresh_only = false); void open_script_create_dialog(const String &p_base_name, const String &p_base_path); void open_text_file_create_dialog(const String &p_base_path, const String &p_base_name = ""); - RES open_file(const String &p_file); + Ref<Resource> open_file(const String &p_file); void ensure_select_current(); - _FORCE_INLINE_ bool edit(const RES &p_resource, bool p_grab_focus = true) { return edit(p_resource, -1, 0, p_grab_focus); } - bool edit(const RES &p_resource, int p_line, int p_col, bool p_grab_focus = true); + _FORCE_INLINE_ bool edit(const Ref<Resource> &p_resource, bool p_grab_focus = true) { return edit(p_resource, -1, 0, p_grab_focus); } + bool edit(const Ref<Resource> &p_resource, int p_line, int p_col, bool p_grab_focus = true); void get_breakpoints(List<String> *p_breakpoints); @@ -505,6 +526,8 @@ public: void goto_help(const String &p_desc) { _help_class_goto(p_desc); } void update_doc(const String &p_name); + void clear_docs_from_script(const Ref<Script> &p_script); + void update_docs_from_script(const Ref<Script> &p_script); bool can_take_away_focus() const; @@ -517,15 +540,14 @@ public: static void register_create_script_editor_function(CreateScriptEditorFunc p_func); - ScriptEditor(EditorNode *p_editor); + ScriptEditor(); ~ScriptEditor(); }; class ScriptEditorPlugin : public EditorPlugin { GDCLASS(ScriptEditorPlugin, EditorPlugin); - ScriptEditor *script_editor; - EditorNode *editor; + ScriptEditor *script_editor = nullptr; public: virtual String get_name() const override { return "Script"; } @@ -538,9 +560,6 @@ public: virtual void save_external_data() override; virtual void apply_changes() override; - virtual void restore_global_state() override; - virtual void save_global_state() override; - virtual void set_window_layout(Ref<ConfigFile> p_layout) override; virtual void get_window_layout(Ref<ConfigFile> p_layout) override; @@ -548,7 +567,7 @@ public: virtual void edited_scene_changed() override; - ScriptEditorPlugin(EditorNode *p_node); + ScriptEditorPlugin(); ~ScriptEditorPlugin(); }; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index ab5c9a0ef1..9fe1d8af99 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1,35 +1,36 @@ -/*************************************************************************/ -/* script_text_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* script_text_editor.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 "script_text_editor.h" +#include "core/config/project_settings.h" #include "core/math/expression.h" #include "core/os/keyboard.h" #include "editor/debugger/editor_debugger_node.h" @@ -37,6 +38,7 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "scene/gui/split_container.h" void ConnectionInfoDialog::ok_pressed() { } @@ -132,11 +134,11 @@ void ScriptTextEditor::apply_code() { code_editor->get_text_editor()->get_syntax_highlighter()->update_cache(); } -RES ScriptTextEditor::get_edited_resource() const { +Ref<Resource> ScriptTextEditor::get_edited_resource() const { return script; } -void ScriptTextEditor::set_edited_resource(const RES &p_res) { +void ScriptTextEditor::set_edited_resource(const Ref<Resource> &p_res) { ERR_FAIL_COND(script.is_valid()); ERR_FAIL_COND(p_res.is_null()); @@ -150,7 +152,7 @@ void ScriptTextEditor::set_edited_resource(const RES &p_res) { code_editor->update_line_and_column(); } -void ScriptTextEditor::enable_editor() { +void ScriptTextEditor::enable_editor(Control *p_shortcut_context) { if (editor_enabled) { return; } @@ -160,6 +162,15 @@ void ScriptTextEditor::enable_editor() { _enable_code_editor(); _validate_script(); + + if (p_shortcut_context) { + for (int i = 0; i < edit_hb->get_child_count(); ++i) { + Control *c = cast_to<Control>(edit_hb->get_child(i)); + if (c) { + c->set_shortcut_context(p_shortcut_context); + } + } + } } void ScriptTextEditor::_load_theme_settings() { @@ -240,13 +251,35 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) { goto_line_centered(p_line.operator int64_t()); } else if (p_line.get_type() == Variant::DICTIONARY) { Dictionary meta = p_line.operator Dictionary(); - code_editor->get_text_editor()->insert_line_at(meta["line"].operator int64_t() - 1, "# warning-ignore:" + meta["code"].operator String()); + const int line = meta["line"].operator int64_t() - 1; + const String code = meta["code"].operator String(); + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; + + CodeEdit *text_editor = code_editor->get_text_editor(); + String prev_line = line > 0 ? text_editor->get_line(line - 1) : ""; + if (prev_line.contains("@warning_ignore")) { + const int closing_bracket_idx = prev_line.find(")"); + const String text_to_insert = ", " + code.quote(quote_style); + prev_line = prev_line.insert(closing_bracket_idx, text_to_insert); + text_editor->set_line(line - 1, prev_line); + } else { + const int indent = text_editor->get_indent_level(line) / text_editor->get_indent_size(); + String annotation_indent; + if (!text_editor->is_indent_using_spaces()) { + annotation_indent = String("\t").repeat(indent); + } else { + annotation_indent = String(" ").repeat(text_editor->get_indent_size() * indent); + } + text_editor->insert_line_at(line, annotation_indent + "@warning_ignore(" + code.quote(quote_style) + ")"); + } + _validate_script(); } } void ScriptTextEditor::_error_clicked(Variant p_line) { if (p_line.get_type() == Variant::INT) { + code_editor->get_text_editor()->remove_secondary_carets(); code_editor->get_text_editor()->set_caret_line(p_line.operator int64_t()); } } @@ -269,11 +302,13 @@ void ScriptTextEditor::reload_text() { te->tag_saved_version(); code_editor->update_line_and_column(); + _validate_script(); } void ScriptTextEditor::add_callback(const String &p_function, PackedStringArray p_args) { String code = code_editor->get_text_editor()->get_text(); int pos = script->get_language()->find_function(p_function, code); + code_editor->get_text_editor()->remove_secondary_carets(); if (pos == -1) { //does not exist code_editor->get_text_editor()->deselect(); @@ -293,7 +328,7 @@ bool ScriptTextEditor::show_members_overview() { } void ScriptTextEditor::update_settings() { - code_editor->get_text_editor()->set_gutter_draw(connection_gutter, EditorSettings::get_singleton()->get("text_editor/appearance/gutters/show_info_gutter")); + code_editor->get_text_editor()->set_gutter_draw(connection_gutter, EDITOR_GET("text_editor/appearance/gutters/show_info_gutter")); code_editor->update_editor_settings(); } @@ -320,10 +355,16 @@ void ScriptTextEditor::set_edit_state(const Variant &p_state) { } if (editor_enabled) { +#ifndef ANDROID_ENABLED ensure_focus(); +#endif } } +Variant ScriptTextEditor::get_navigation_state() { + return code_editor->get_navigation_state(); +} + void ScriptTextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { code_editor->convert_case(p_case); } @@ -396,8 +437,17 @@ String ScriptTextEditor::get_name() { } Ref<Texture2D> ScriptTextEditor::get_theme_icon() { - if (get_parent_control() && get_parent_control()->has_theme_icon(script->get_class(), "EditorIcons")) { - return get_parent_control()->get_theme_icon(script->get_class(), "EditorIcons"); + if (get_parent_control()) { + String icon_name = script->get_class(); + if (script->is_built_in()) { + icon_name += "Internal"; + } + + if (get_parent_control()->has_theme_icon(icon_name, SNAME("EditorIcons"))) { + return get_parent_control()->get_theme_icon(icon_name, SNAME("EditorIcons")); + } else if (get_parent_control()->has_theme_icon(script->get_class(), SNAME("EditorIcons"))) { + return get_parent_control()->get_theme_icon(script->get_class(), SNAME("EditorIcons")); + } } return Ref<Texture2D>(); @@ -408,12 +458,14 @@ void ScriptTextEditor::_validate_script() { String text = te->get_text(); List<String> fnc; - Set<int> safe_lines; - List<ScriptLanguage::Warning> warnings; - List<ScriptLanguage::ScriptError> errors; + + warnings.clear(); + errors.clear(); + safe_lines.clear(); if (!script->get_language()->validate(text, script->get_path(), &fnc, &errors, &warnings, &safe_lines)) { - String error_text = TTR("Error at ") + "(" + itos(errors[0].line) + "," + itos(errors[0].column) + "): " + errors[0].message; + // TRANSLATORS: Script error pointing to a line and column number. + String error_text = vformat(TTR("Error at (%d, %d):"), errors[0].line, errors[0].column) + " " + errors[0].message; code_editor->set_error(error_text); code_editor->set_error_pos(errors[0].line - 1, errors[0].column - 1); script_is_valid = false; @@ -432,7 +484,14 @@ void ScriptTextEditor::_validate_script() { script_is_valid = true; } _update_connected_methods(); + _update_warnings(); + _update_errors(); + + emit_signal(SNAME("name_changed")); + emit_signal(SNAME("edited_script_changed")); +} +void ScriptTextEditor::_update_warnings() { int warning_nb = warnings.size(); warnings_panel->clear(); @@ -460,7 +519,6 @@ void ScriptTextEditor::_validate_script() { } } - code_editor->set_error_count(errors.size()); code_editor->set_warning_count(warning_nb); if (has_connections_table) { @@ -476,7 +534,7 @@ void ScriptTextEditor::_validate_script() { warnings_panel->push_cell(); warnings_panel->push_meta(ignore_meta); warnings_panel->push_color( - warnings_panel->get_theme_color(SNAME("accent_color"), SNAME("Editor")).lerp(warnings_panel->get_theme_color(SNAME("mono_color"), SNAME("Editor")), 0.5)); + warnings_panel->get_theme_color(SNAME("accent_color"), SNAME("Editor")).lerp(warnings_panel->get_theme_color(SNAME("mono_color"), SNAME("Editor")), 0.5f)); warnings_panel->add_text(TTR("[Ignore]")); warnings_panel->pop(); // Color. warnings_panel->pop(); // Meta ignore. @@ -496,6 +554,10 @@ void ScriptTextEditor::_validate_script() { warnings_panel->pop(); // Cell. } warnings_panel->pop(); // Table. +} + +void ScriptTextEditor::_update_errors() { + code_editor->set_error_count(errors.size()); errors_panel->clear(); errors_panel->push_table(2); @@ -514,7 +576,8 @@ void ScriptTextEditor::_validate_script() { } errors_panel->pop(); // Table - bool highlight_safe = EDITOR_DEF("text_editor/appearance/gutters/highlight_type_safe_lines", true); + CodeEdit *te = code_editor->get_text_editor(); + bool highlight_safe = EDITOR_GET("text_editor/appearance/gutters/highlight_type_safe_lines"); bool last_is_safe = false; for (int i = 0; i < te->get_line_count(); i++) { if (errors.is_empty()) { @@ -543,9 +606,6 @@ void ScriptTextEditor::_validate_script() { te->set_line_gutter_item_color(i, 1, default_line_number_color); } } - - emit_signal(SNAME("name_changed")); - emit_signal(SNAME("edited_script_changed")); } void ScriptTextEditor::_update_bookmark_list() { @@ -557,7 +617,7 @@ void ScriptTextEditor::_update_bookmark_list() { bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT); bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV); - Array bookmark_list = code_editor->get_text_editor()->get_bookmarked_lines(); + PackedInt32Array bookmark_list = code_editor->get_text_editor()->get_bookmarked_lines(); if (bookmark_list.size() == 0) { return; } @@ -575,7 +635,7 @@ void ScriptTextEditor::_update_bookmark_list() { } bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - `" + line + "`"); - bookmarks_menu->set_item_metadata(bookmarks_menu->get_item_count() - 1, bookmark_list[i]); + bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); } } @@ -625,7 +685,7 @@ static Node *_find_node_for_script(Node *p_base, Node *p_current, const Ref<Scri return nullptr; } -static void _find_changed_scripts_for_external_editor(Node *p_base, Node *p_current, Set<Ref<Script>> &r_scripts) { +static void _find_changed_scripts_for_external_editor(Node *p_base, Node *p_current, HashSet<Ref<Script>> &r_scripts) { if (p_current->get_owner() != p_base && p_base != p_current) { return; } @@ -641,51 +701,51 @@ static void _find_changed_scripts_for_external_editor(Node *p_base, Node *p_curr } void ScriptEditor::_update_modified_scripts_for_external_editor(Ref<Script> p_for_script) { - if (!bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor"))) { + if (!bool(EDITOR_GET("text_editor/external/use_external_editor"))) { return; } ERR_FAIL_COND(!get_tree()); - Set<Ref<Script>> scripts; + HashSet<Ref<Script>> scripts; Node *base = get_tree()->get_edited_scene_root(); if (base) { _find_changed_scripts_for_external_editor(base, base, scripts); } - for (Set<Ref<Script>>::Element *E = scripts.front(); E; E = E->next()) { - Ref<Script> script = E->get(); + for (const Ref<Script> &E : scripts) { + Ref<Script> scr = E; - if (p_for_script.is_valid() && p_for_script != script) { + if (p_for_script.is_valid() && p_for_script != scr) { continue; } - if (script->is_built_in()) { + if (scr->is_built_in()) { continue; //internal script, who cares, though weird } - uint64_t last_date = script->get_last_modified_time(); - uint64_t date = FileAccess::get_modified_time(script->get_path()); + uint64_t last_date = scr->get_last_modified_time(); + uint64_t date = FileAccess::get_modified_time(scr->get_path()); if (last_date != date) { - Ref<Script> rel_script = ResourceLoader::load(script->get_path(), script->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); - ERR_CONTINUE(!rel_script.is_valid()); - script->set_source_code(rel_script->get_source_code()); - script->set_last_modified_time(rel_script->get_last_modified_time()); - script->update_exports(); + Ref<Script> rel_scr = ResourceLoader::load(scr->get_path(), scr->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_CONTINUE(!rel_scr.is_valid()); + scr->set_source_code(rel_scr->get_source_code()); + scr->set_last_modified_time(rel_scr->get_last_modified_time()); + scr->update_exports(); _trigger_live_script_reload(); } } } -void ScriptTextEditor::_code_complete_scripts(void *p_ud, const String &p_code, List<ScriptCodeCompletionOption> *r_options, bool &r_force) { +void ScriptTextEditor::_code_complete_scripts(void *p_ud, const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force) { ScriptTextEditor *ste = (ScriptTextEditor *)p_ud; ste->_code_complete_script(p_code, r_options, r_force); } -void ScriptTextEditor::_code_complete_script(const String &p_code, List<ScriptCodeCompletionOption> *r_options, bool &r_force) { +void ScriptTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force) { if (color_panel->is_visible()) { return; } @@ -695,6 +755,9 @@ void ScriptTextEditor::_code_complete_script(const String &p_code, List<ScriptCo } String hint; Error err = script->get_language()->complete_code(p_code, script->get_path(), base, r_options, r_force, hint); + + r_options->sort_custom_inplace<CodeCompletionOptionCompare>(); + if (err == OK) { code_editor->get_text_editor()->set_code_hint(hint); } @@ -709,7 +772,7 @@ void ScriptTextEditor::_update_breakpoint_list() { breakpoints_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_breakpoint"), DEBUG_GOTO_NEXT_BREAKPOINT); breakpoints_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_breakpoint"), DEBUG_GOTO_PREV_BREAKPOINT); - Array breakpoint_list = code_editor->get_text_editor()->get_breakpointed_lines(); + PackedInt32Array breakpoint_list = code_editor->get_text_editor()->get_breakpointed_lines(); if (breakpoint_list.size() == 0) { return; } @@ -727,7 +790,7 @@ void ScriptTextEditor::_update_breakpoint_list() { } breakpoints_menu->add_item(String::num((int)breakpoint_list[i] + 1) + " - `" + line + "`"); - breakpoints_menu->set_item_metadata(breakpoints_menu->get_item_count() - 1, breakpoint_list[i]); + breakpoints_menu->set_item_metadata(-1, breakpoint_list[i]); } } @@ -767,7 +830,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c _goto_line(p_row); switch (result.type) { - case ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION: { + case ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION: { if (result.script.is_valid()) { emit_signal(SNAME("request_open_script_at_line"), result.script, result.location - 1); } else { @@ -775,10 +838,10 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c goto_line_centered(result.location - 1); } } break; - case ScriptLanguage::LookupResult::RESULT_CLASS: { + case ScriptLanguage::LOOKUP_RESULT_CLASS: { emit_signal(SNAME("go_to_help"), "class_name:" + result.class_name); } break; - case ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT: { + case ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT: { StringName cname = result.class_name; bool success; while (true) { @@ -794,11 +857,11 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c emit_signal(SNAME("go_to_help"), "class_constant:" + result.class_name + ":" + result.class_member); } break; - case ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY: { + case ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY: { emit_signal(SNAME("go_to_help"), "class_property:" + result.class_name + ":" + result.class_member); } break; - case ScriptLanguage::LookupResult::RESULT_CLASS_METHOD: { + case ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD: { StringName cname = result.class_name; while (true) { @@ -813,7 +876,22 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c emit_signal(SNAME("go_to_help"), "class_method:" + result.class_name + ":" + result.class_member); } break; - case ScriptLanguage::LookupResult::RESULT_CLASS_ENUM: { + case ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL: { + StringName cname = result.class_name; + + while (true) { + if (ClassDB::has_signal(cname, result.class_member)) { + result.class_name = cname; + cname = ClassDB::get_parent_class(cname); + } else { + break; + } + } + + emit_signal(SNAME("go_to_help"), "class_signal:" + result.class_name + ":" + result.class_member); + + } break; + case ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM: { StringName cname = result.class_name; StringName success; while (true) { @@ -829,9 +907,14 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c emit_signal(SNAME("go_to_help"), "class_enum:" + result.class_name + ":" + result.class_member); } break; - case ScriptLanguage::LookupResult::RESULT_CLASS_TBD_GLOBALSCOPE: { + case ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION: { + emit_signal(SNAME("go_to_help"), "class_annotation:" + result.class_name + ":" + result.class_member); + } break; + case ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE: { emit_signal(SNAME("go_to_help"), "class_global:" + result.class_name + ":" + result.class_member); } break; + default: { + } } } else if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) { // Check for Autoload scenes. @@ -880,7 +963,7 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) { String ScriptTextEditor::_get_absolute_path(const String &rel_path) { String base_path = script->get_path().get_base_dir(); - String path = base_path.plus_file(rel_path); + String path = base_path.path_join(rel_path); return path.replace("///", "//").simplify_path(); } @@ -892,10 +975,7 @@ void ScriptTextEditor::_update_connected_methods() { CodeEdit *text_edit = code_editor->get_text_editor(); text_edit->set_gutter_width(connection_gutter, text_edit->get_line_height()); for (int i = 0; i < text_edit->get_line_count(); i++) { - if (text_edit->get_line_gutter_metadata(i, connection_gutter) == "") { - continue; - } - text_edit->set_line_gutter_metadata(i, connection_gutter, ""); + text_edit->set_line_gutter_metadata(i, connection_gutter, Dictionary()); text_edit->set_line_gutter_icon(i, connection_gutter, nullptr); text_edit->set_line_gutter_clickable(i, connection_gutter, false); } @@ -910,13 +990,14 @@ void ScriptTextEditor::_update_connected_methods() { return; } + // Add connection icons to methods. Vector<Node *> nodes = _find_all_node_for_script(base, base, script); - Set<StringName> methods_found; + HashSet<StringName> methods_found; for (int i = 0; i < nodes.size(); i++) { - List<Connection> connections; - nodes[i]->get_signals_connected_to_this(&connections); + List<Connection> signal_connections; + nodes[i]->get_signals_connected_to_this(&signal_connections); - for (const Connection &connection : connections) { + for (const Connection &connection : signal_connections) { if (!(connection.flags & CONNECT_PERSIST)) { continue; } @@ -927,21 +1008,25 @@ void ScriptTextEditor::_update_connected_methods() { continue; } - if (methods_found.has(connection.callable.get_method())) { + const StringName method = connection.callable.get_method(); + if (methods_found.has(method)) { continue; } - if (!ClassDB::has_method(script->get_instance_base_type(), connection.callable.get_method())) { + if (!ClassDB::has_method(script->get_instance_base_type(), method)) { int line = -1; for (int j = 0; j < functions.size(); j++) { String name = functions[j].get_slice(":", 0); - if (name == connection.callable.get_method()) { + if (name == method) { + Dictionary line_meta; + line_meta["type"] = "connection"; + line_meta["method"] = method; line = functions[j].get_slice(":", 1).to_int() - 1; - text_edit->set_line_gutter_metadata(line, connection_gutter, connection.callable.get_method()); + text_edit->set_line_gutter_metadata(line, connection_gutter, line_meta); text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_theme_icon(SNAME("Slot"), SNAME("EditorIcons"))); text_edit->set_line_gutter_clickable(line, connection_gutter, true); - methods_found.insert(connection.callable.get_method()); + methods_found.insert(method); break; } } @@ -954,7 +1039,7 @@ void ScriptTextEditor::_update_connected_methods() { bool found_inherited_function = false; Ref<Script> inherited_script = script->get_base_script(); while (!inherited_script.is_null()) { - if (inherited_script->has_method(connection.callable.get_method())) { + if (inherited_script->has_method(method)) { found_inherited_function = true; break; } @@ -968,6 +1053,66 @@ void ScriptTextEditor::_update_connected_methods() { } } } + + // Add override icons to methods. + methods_found.clear(); + for (int i = 0; i < functions.size(); i++) { + StringName name = StringName(functions[i].get_slice(":", 0)); + if (methods_found.has(name)) { + continue; + } + + String found_base_class; + StringName base_class = script->get_instance_base_type(); + Ref<Script> inherited_script = script->get_base_script(); + while (!inherited_script.is_null()) { + if (inherited_script->has_method(name)) { + found_base_class = "script:" + inherited_script->get_path(); + break; + } + + base_class = inherited_script->get_instance_base_type(); + inherited_script = inherited_script->get_base_script(); + } + + if (found_base_class.is_empty()) { + while (base_class) { + List<MethodInfo> methods; + ClassDB::get_method_list(base_class, &methods, true); + for (int j = 0; j < methods.size(); j++) { + if (methods[j].name == name) { + found_base_class = "builtin:" + base_class; + break; + } + } + + ClassDB::ClassInfo *base_class_ptr = ClassDB::classes.getptr(base_class)->inherits_ptr; + if (base_class_ptr == nullptr) { + break; + } + base_class = base_class_ptr->name; + } + } + + if (!found_base_class.is_empty()) { + int line = functions[i].get_slice(":", 1).to_int() - 1; + + Dictionary line_meta = text_edit->get_line_gutter_metadata(line, connection_gutter); + if (line_meta.is_empty()) { + // Add override icon to gutter. + line_meta["type"] = "inherits"; + line_meta["method"] = name; + line_meta["base_class"] = found_base_class; + text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_theme_icon(SNAME("MethodOverride"), SNAME("EditorIcons"))); + text_edit->set_line_gutter_clickable(line, connection_gutter, true); + } else { + // If method is also connected to signal, then merge icons and keep the click behavior of the slot. + text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_theme_icon(SNAME("MethodOverrideAndSlot"), SNAME("EditorIcons"))); + } + + methods_found.insert(name); + } + } } void ScriptTextEditor::_update_gutter_indexes() { @@ -989,18 +1134,40 @@ void ScriptTextEditor::_gutter_clicked(int p_line, int p_gutter) { return; } - String method = code_editor->get_text_editor()->get_line_gutter_metadata(p_line, p_gutter); - if (method.is_empty()) { + Dictionary meta = code_editor->get_text_editor()->get_line_gutter_metadata(p_line, p_gutter); + String type = meta.get("type", ""); + if (type.is_empty()) { return; } - Node *base = get_tree()->get_edited_scene_root(); - if (!base) { + // All types currently need a method name. + String method = meta.get("method", ""); + if (method.is_empty()) { return; } - Vector<Node *> nodes = _find_all_node_for_script(base, base, script); - connection_info_dialog->popup_connections(method, nodes); + if (type == "connection") { + Node *base = get_tree()->get_edited_scene_root(); + if (!base) { + return; + } + + Vector<Node *> nodes = _find_all_node_for_script(base, base, script); + connection_info_dialog->popup_connections(method, nodes); + } else if (type == "inherits") { + String base_class_raw = meta["base_class"]; + PackedStringArray base_class_split = base_class_raw.split(":", true, 1); + + if (base_class_split[0] == "script") { + // Go to function declaration. + Ref<Script> base_script = ResourceLoader::load(base_class_split[1]); + ERR_FAIL_COND(!base_script.is_valid()); + emit_signal(SNAME("go_to_method"), base_script, method); + } else if (base_class_split[0] == "builtin") { + // Open method documentation. + emit_signal(SNAME("go_to_help"), "class_method:" + base_class_split[1] + ":" + method); + } + } } void ScriptTextEditor::_edit_option(int p_op) { @@ -1037,21 +1204,19 @@ void ScriptTextEditor::_edit_option(int p_op) { case EDIT_MOVE_LINE_DOWN: { code_editor->move_lines_down(); } break; - case EDIT_INDENT_LEFT: { + case EDIT_INDENT: { Ref<Script> scr = script; if (scr.is_null()) { return; } - - tx->unindent_lines(); + tx->indent_lines(); } break; - case EDIT_INDENT_RIGHT: { + case EDIT_UNINDENT: { Ref<Script> scr = script; if (scr.is_null()) { return; } - - tx->indent_lines(); + tx->unindent_lines(); } break; case EDIT_DELETE_LINE: { code_editor->delete_lines(); @@ -1060,16 +1225,18 @@ void ScriptTextEditor::_edit_option(int p_op) { code_editor->duplicate_selection(); } break; case EDIT_TOGGLE_FOLD_LINE: { - tx->toggle_foldable_line(tx->get_caret_line()); - tx->update(); + for (int caret_idx = 0; caret_idx < tx->get_caret_count(); caret_idx++) { + tx->toggle_foldable_line(tx->get_caret_line(caret_idx)); + } + tx->queue_redraw(); } break; case EDIT_FOLD_ALL_LINES: { tx->fold_all_lines(); - tx->update(); + tx->queue_redraw(); } break; case EDIT_UNFOLD_ALL_LINES: { tx->unfold_all_lines(); - tx->update(); + tx->queue_redraw(); } break; case EDIT_TOGGLE_COMMENT: { _edit_option_toggle_inline_comment(); @@ -1128,28 +1295,28 @@ void ScriptTextEditor::_edit_option(int p_op) { } break; case EDIT_EVALUATE: { Expression expression; - Vector<String> lines = code_editor->get_text_editor()->get_selected_text().split("\n"); - PackedStringArray results; - - for (int i = 0; i < lines.size(); i++) { - String line = lines[i]; - String whitespace = line.substr(0, line.size() - line.strip_edges(true, false).size()); //extract the whitespace at the beginning - - if (expression.parse(line) == OK) { - Variant result = expression.execute(Array(), Variant(), false); - if (expression.get_error_text().is_empty()) { - results.push_back(whitespace + result.get_construct_string()); + tx->begin_complex_operation(); + for (int caret_idx = 0; caret_idx < tx->get_caret_count(); caret_idx++) { + Vector<String> lines = tx->get_selected_text(caret_idx).split("\n"); + PackedStringArray results; + + for (int i = 0; i < lines.size(); i++) { + String line = lines[i]; + String whitespace = line.substr(0, line.size() - line.strip_edges(true, false).size()); // Extract the whitespace at the beginning. + if (expression.parse(line) == OK) { + Variant result = expression.execute(Array(), Variant(), false, true); + if (expression.get_error_text().is_empty()) { + results.push_back(whitespace + result.get_construct_string()); + } else { + results.push_back(line); + } } else { results.push_back(line); } - } else { - results.push_back(line); } + tx->insert_text_at_caret(String("\n").join(results), caret_idx); } - - code_editor->get_text_editor()->begin_complex_operation(); //prevents creating a two-step undo - code_editor->get_text_editor()->insert_text_at_caret(String("\n").join(results)); - code_editor->get_text_editor()->end_complex_operation(); + tx->end_complex_operation(); } break; case SEARCH_FIND: { code_editor->get_find_replace_bar()->popup_search(); @@ -1164,14 +1331,14 @@ void ScriptTextEditor::_edit_option(int p_op) { code_editor->get_find_replace_bar()->popup_replace(); } break; case SEARCH_IN_FILES: { - String selected_text = code_editor->get_text_editor()->get_selected_text(); + String selected_text = tx->get_selected_text(); // Yep, because it doesn't make sense to instance this dialog for every single script open... // So this will be delegated to the ScriptEditor. emit_signal(SNAME("search_in_files_requested"), selected_text); } break; case REPLACE_IN_FILES: { - String selected_text = code_editor->get_text_editor()->get_selected_text(); + String selected_text = tx->get_selected_text(); emit_signal(SNAME("replace_in_files_requested"), selected_text); } break; @@ -1195,13 +1362,40 @@ void ScriptTextEditor::_edit_option(int p_op) { code_editor->remove_all_bookmarks(); } break; case DEBUG_TOGGLE_BREAKPOINT: { - int line = tx->get_caret_line(); - bool dobreak = !tx->is_line_breakpointed(line); - tx->set_line_as_breakpoint(line, dobreak); - EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), line + 1, dobreak); + Vector<int> caret_edit_order = tx->get_caret_index_edit_order(); + caret_edit_order.reverse(); + int last_line = -1; + for (const int &c : caret_edit_order) { + int from = tx->has_selection(c) ? tx->get_selection_from_line(c) : tx->get_caret_line(c); + from += from == last_line ? 1 : 0; + int to = tx->has_selection(c) ? tx->get_selection_to_line(c) : tx->get_caret_line(c); + if (to < from) { + continue; + } + // Check first if there's any lines with breakpoints in the selection. + bool selection_has_breakpoints = false; + for (int line = from; line <= to; line++) { + if (tx->is_line_breakpointed(line)) { + selection_has_breakpoints = true; + break; + } + } + + // Set breakpoint on caret or remove all bookmarks from the selection. + if (!selection_has_breakpoints) { + if (tx->get_caret_line(c) != last_line) { + tx->set_line_as_breakpoint(tx->get_caret_line(c), true); + } + } else { + for (int line = from; line <= to; line++) { + tx->set_line_as_breakpoint(line, false); + } + } + last_line = to; + } } break; case DEBUG_REMOVE_ALL_BREAKPOINTS: { - Array bpoints = tx->get_breakpointed_lines(); + PackedInt32Array bpoints = tx->get_breakpointed_lines(); for (int i = 0; i < bpoints.size(); i++) { int line = bpoints[i]; @@ -1211,72 +1405,51 @@ void ScriptTextEditor::_edit_option(int p_op) { } } break; case DEBUG_GOTO_NEXT_BREAKPOINT: { - Array bpoints = tx->get_breakpointed_lines(); + PackedInt32Array bpoints = tx->get_breakpointed_lines(); if (bpoints.size() <= 0) { return; } - int line = tx->get_caret_line(); - - // wrap around - if (line >= (int)bpoints[bpoints.size() - 1]) { - tx->unfold_line(bpoints[0]); - tx->set_caret_line(bpoints[0]); - tx->center_viewport_to_caret(); - } else { - for (int i = 0; i < bpoints.size(); i++) { - int bline = bpoints[i]; - if (bline > line) { - tx->unfold_line(bline); - tx->set_caret_line(bline); - tx->center_viewport_to_caret(); - return; - } + int current_line = tx->get_caret_line(); + int bpoint_idx = 0; + if (current_line < (int)bpoints[bpoints.size() - 1]) { + while (bpoint_idx < bpoints.size() && bpoints[bpoint_idx] <= current_line) { + bpoint_idx++; } } - + code_editor->goto_line_centered(bpoints[bpoint_idx]); } break; case DEBUG_GOTO_PREV_BREAKPOINT: { - Array bpoints = tx->get_breakpointed_lines(); + PackedInt32Array bpoints = tx->get_breakpointed_lines(); if (bpoints.size() <= 0) { return; } - int line = tx->get_caret_line(); - // wrap around - if (line <= (int)bpoints[0]) { - tx->unfold_line(bpoints[bpoints.size() - 1]); - tx->set_caret_line(bpoints[bpoints.size() - 1]); - tx->center_viewport_to_caret(); - } else { - for (int i = bpoints.size() - 1; i >= 0; i--) { - int bline = bpoints[i]; - if (bline < line) { - tx->unfold_line(bline); - tx->set_caret_line(bline); - tx->center_viewport_to_caret(); - return; - } + int current_line = tx->get_caret_line(); + int bpoint_idx = bpoints.size() - 1; + if (current_line > (int)bpoints[0]) { + while (bpoint_idx >= 0 && bpoints[bpoint_idx] >= current_line) { + bpoint_idx--; } } - + code_editor->goto_line_centered(bpoints[bpoint_idx]); } break; case HELP_CONTEXTUAL: { - String text = tx->get_selected_text(); + String text = tx->get_selected_text(0); if (text.is_empty()) { - text = tx->get_word_under_caret(); + text = tx->get_word_under_caret(0); } if (!text.is_empty()) { emit_signal(SNAME("request_help"), text); } } break; case LOOKUP_SYMBOL: { - String text = tx->get_word_under_caret(); + String text = tx->get_word_under_caret(0); if (text.is_empty()) { - text = tx->get_selected_text(); + text = tx->get_selected_text(0); } if (!text.is_empty()) { - _lookup_symbol(text, tx->get_caret_line(), tx->get_caret_column()); + _lookup_symbol(text, tx->get_caret_line(0), tx->get_caret_column(0)); } } break; } @@ -1292,7 +1465,7 @@ void ScriptTextEditor::_edit_option_toggle_inline_comment() { script->get_language()->get_comment_delimiters(&comment_delimiters); for (const String &script_delimiter : comment_delimiters) { - if (script_delimiter.find(" ") == -1) { + if (!script_delimiter.contains(" ")) { delimiter = script_delimiter; break; } @@ -1311,11 +1484,11 @@ void ScriptTextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_hig void ScriptTextEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { ERR_FAIL_COND(p_highlighter.is_null()); - Map<String, Ref<EditorSyntaxHighlighter>>::Element *el = highlighters.front(); - while (el != nullptr) { - int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key()); - highlighter_menu->set_item_checked(highlighter_index, el->value() == p_highlighter); - el = el->next(); + HashMap<String, Ref<EditorSyntaxHighlighter>>::Iterator el = highlighters.begin(); + while (el) { + int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key); + highlighter_menu->set_item_checked(highlighter_index, el->value == p_highlighter); + ++el; } CodeEdit *te = code_editor->get_text_editor(); @@ -1330,28 +1503,28 @@ void ScriptTextEditor::_change_syntax_highlighter(int p_idx) { void ScriptTextEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: + if (!editor_enabled) { + break; + } + if (is_visible_in_tree()) { + _update_warnings(); + _update_errors(); + } + [[fallthrough]]; case NOTIFICATION_ENTER_TREE: { code_editor->get_text_editor()->set_gutter_width(connection_gutter, code_editor->get_text_editor()->get_line_height()); } break; - default: - break; } } -void ScriptTextEditor::_bind_methods() { - ClassDB::bind_method("_update_connected_methods", &ScriptTextEditor::_update_connected_methods); - - ClassDB::bind_method("_get_drag_data_fw", &ScriptTextEditor::get_drag_data_fw); - ClassDB::bind_method("_can_drop_data_fw", &ScriptTextEditor::can_drop_data_fw); - ClassDB::bind_method("_drop_data_fw", &ScriptTextEditor::drop_data_fw); -} - Control *ScriptTextEditor::get_edit_menu() { return edit_hb; } void ScriptTextEditor::clear_edit_menu() { - memdelete(edit_hb); + if (editor_enabled) { + memdelete(edit_hb); + } } void ScriptTextEditor::set_find_replace_bar(FindReplaceBar *p_bar) { @@ -1365,12 +1538,12 @@ void ScriptTextEditor::reload(bool p_soft) { return; } scr->set_source_code(te->get_text()); - bool soft = p_soft || scr->get_instance_base_type() == "EditorPlugin"; //always soft-reload editor plugins + bool soft = p_soft || scr->get_instance_base_type() == "EditorPlugin"; // Always soft-reload editor plugins. scr->get_language()->reload_tool_script(scr, soft); } -Array ScriptTextEditor::get_breakpoints() { +PackedInt32Array ScriptTextEditor::get_breakpoints() { return code_editor->get_text_editor()->get_breakpointed_lines(); } @@ -1382,8 +1555,10 @@ void ScriptTextEditor::clear_breakpoints() { code_editor->get_text_editor()->clear_breakpointed_lines(); } -void ScriptTextEditor::set_tooltip_request_func(String p_method, Object *p_obj) { - code_editor->get_text_editor()->set_tooltip_request_func(p_obj, p_method, this); +void ScriptTextEditor::set_tooltip_request_func(const Callable &p_toolip_callback) { + Variant args[1] = { this }; + const Variant *argp[] = { &args[0] }; + code_editor->get_text_editor()->set_tooltip_request_func(p_toolip_callback.bindp(argp, 1)); } void ScriptTextEditor::set_debugger_active(bool p_active) { @@ -1412,16 +1587,17 @@ bool ScriptTextEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_ } static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const Ref<Script> &script) { - if (p_edited_scene != p_current_node && p_current_node->get_owner() != p_edited_scene) { - return nullptr; - } - - Ref<Script> scr = p_current_node->get_script(); - - if (scr.is_valid() && scr == script) { - return p_current_node; + // Check scripts only for the nodes belonging to the edited scene. + if (p_current_node == p_edited_scene || p_current_node->get_owner() == p_edited_scene) { + Ref<Script> scr = p_current_node->get_script(); + if (scr.is_valid() && scr == script) { + return p_current_node; + } } + // Traverse all children, even the ones not owned by the edited scene as they + // can still have child nodes added within the edited scene and thus owned by + // it (e.g. nodes added to subscene's root or to its editable children). for (int i = 0; i < p_current_node->get_child_count(); i++) { Node *n = _find_script_node(p_edited_scene, p_current_node->get_child(i), script); if (n) { @@ -1432,9 +1608,24 @@ static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const return nullptr; } -void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { - const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; +static String _quote_drop_data(const String &str) { + // This function prepares a string for being "dropped" into the script editor. + // The string can be a resource path, node path or property name. + + const bool using_single_quotes = EDITOR_GET("text_editor/completion/use_single_quotes"); + + String escaped = str.c_escape(); + + // If string is double quoted, there is no need to escape single quotes. + // We can revert the extra escaping added in c_escape(). + if (!using_single_quotes) { + escaped = escaped.replace("\\'", "\'"); + } + + return escaped.quote(using_single_quotes ? "'" : "\""); +} +void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { Dictionary d = p_data; CodeEdit *te = code_editor->get_text_editor(); @@ -1443,6 +1634,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data int col = pos.x; if (d.has("type") && String(d["type"]) == "resource") { + te->remove_secondary_carets(); Ref<Resource> res = d["resource"]; if (!res.is_valid()) { return; @@ -1456,9 +1648,11 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data te->set_caret_line(row); te->set_caret_column(col); te->insert_text_at_caret(res->get_path()); + te->grab_focus(); } if (d.has("type") && (String(d["type"]) == "files" || String(d["type"]) == "files_and_dirs")) { + te->remove_secondary_carets(); Array files = d["files"]; String text_to_drop; @@ -1469,20 +1663,27 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data } if (preload) { - text_to_drop += "preload(" + String(files[i]).c_escape().quote(quote_style) + ")"; + text_to_drop += "preload(" + _quote_drop_data(String(files[i])) + ")"; } else { - text_to_drop += String(files[i]).c_escape().quote(quote_style); + text_to_drop += _quote_drop_data(String(files[i])); } } te->set_caret_line(row); te->set_caret_column(col); te->insert_text_at_caret(text_to_drop); + te->grab_focus(); } if (d.has("type") && String(d["type"]) == "nodes") { - Node *sn = _find_script_node(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root(), script); + te->remove_secondary_carets(); + Node *scene_root = get_tree()->get_edited_scene_root(); + if (!scene_root) { + EditorNode::get_singleton()->show_warning(TTR("Can't drop nodes without an open scene.")); + return; + } + Node *sn = _find_script_node(scene_root, scene_root, script); if (!sn) { EditorNode::get_singleton()->show_warning(vformat(TTR("Can't drop nodes because script '%s' is not used in this scene."), get_name())); return; @@ -1490,32 +1691,85 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data Array nodes = d["nodes"]; String text_to_drop; - for (int i = 0; i < nodes.size(); i++) { - if (i > 0) { - text_to_drop += ","; - } - NodePath np = nodes[i]; - Node *node = get_node(np); - if (!node) { - continue; + if (Input::get_singleton()->is_key_pressed(Key::CTRL)) { + bool use_type = EDITOR_GET("text_editor/completion/add_type_hints"); + for (int i = 0; i < nodes.size(); i++) { + NodePath np = nodes[i]; + Node *node = get_node(np); + if (!node) { + continue; + } + + bool is_unique = false; + String path; + if (node->is_unique_name_in_owner()) { + path = node->get_name(); + is_unique = true; + } else { + path = sn->get_path_to(node); + } + for (const String &segment : path.split("/")) { + if (!segment.is_valid_identifier()) { + path = _quote_drop_data(path); + break; + } + } + + String variable_name = String(node->get_name()).to_snake_case().validate_identifier(); + if (use_type) { + text_to_drop += vformat("@onready var %s: %s = %s%s\n", variable_name, node->get_class_name(), is_unique ? "%" : "$", path); + } else { + text_to_drop += vformat("@onready var %s = %s%s\n", variable_name, is_unique ? "%" : "$", path); + } } + } else { + for (int i = 0; i < nodes.size(); i++) { + if (i > 0) { + text_to_drop += ", "; + } + + NodePath np = nodes[i]; + Node *node = get_node(np); + if (!node) { + continue; + } + + bool is_unique = false; + String path; + if (node->is_unique_name_in_owner()) { + path = node->get_name(); + is_unique = true; + } else { + path = sn->get_path_to(node); + } - String path = sn->get_path_to(node); - text_to_drop += path.c_escape().quote(quote_style); + for (const String &segment : path.split("/")) { + if (!segment.is_valid_identifier()) { + path = _quote_drop_data(path); + break; + } + } + text_to_drop += (is_unique ? "%" : "$") + path; + } } te->set_caret_line(row); te->set_caret_column(col); te->insert_text_at_caret(text_to_drop); + te->grab_focus(); } if (d.has("type") && String(d["type"]) == "obj_property") { - const String text_to_drop = String(d["property"]).c_escape().quote(quote_style); + te->remove_secondary_carets(); + // It is unclear whether properties may contain single or double quotes. + // Assume here that double-quotes may not exist. We are escaping single-quotes if necessary. + const String text_to_drop = _quote_drop_data(String(d["property"])); te->set_caret_line(row); te->set_caret_column(col); te->insert_text_at_caret(text_to_drop); + te->grab_focus(); } } @@ -1530,8 +1784,8 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { local_pos = mb->get_global_position() - tx->get_global_position(); create_menu = true; } else if (k.is_valid() && k->is_action("ui_menu", true)) { - tx->adjust_viewport_to_caret(); - local_pos = tx->get_caret_draw_pos(); + tx->adjust_viewport_to_caret(0); + local_pos = tx->get_caret_draw_pos(0); create_menu = true; } @@ -1540,8 +1794,9 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { int row = pos.y; int col = pos.x; - tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click")); + tx->set_move_caret_on_right_click_enabled(EDITOR_GET("text_editor/behavior/navigation/move_caret_on_right_click")); if (tx->is_move_caret_on_right_click_enabled()) { + tx->remove_secondary_carets(); if (tx->has_selection()) { int from_line = tx->get_selection_from_line(); int to_line = tx->get_selection_to_line(); @@ -1561,10 +1816,10 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { String word_at_pos = tx->get_word_at_pos(local_pos); if (word_at_pos.is_empty()) { - word_at_pos = tx->get_word_under_caret(); + word_at_pos = tx->get_word_under_caret(0); } if (word_at_pos.is_empty()) { - word_at_pos = tx->get_selected_text(); + word_at_pos = tx->get_selected_text(0); } bool has_color = (word_at_pos == "Color"); @@ -1580,7 +1835,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { base = _find_node_for_script(base, base, script); } ScriptLanguage::LookupResult result; - if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), word_at_pos, script->get_path(), base, result) == OK) { + if (script->get_language()->lookup_code(tx->get_text_for_symbol_lookup(), word_at_pos, script->get_path(), base, result) == OK) { open_docs = true; } } @@ -1590,30 +1845,72 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { color_position.x = row; color_position.y = col; - int begin = 0; - int end = 0; - bool valid = false; + int begin = -1; + int end = -1; + enum EXPRESSION_PATTERNS { + NOT_PARSED, + RGBA_PARAMETER, // Color(float,float,float) or Color(float,float,float,float) + COLOR_NAME, // Color.COLOR_NAME + } expression_pattern = NOT_PARSED; + for (int i = col; i < line.length(); i++) { if (line[i] == '(') { + if (expression_pattern == NOT_PARSED) { + begin = i; + expression_pattern = RGBA_PARAMETER; + } else { + // Method call or '(' appearing twice. + expression_pattern = NOT_PARSED; + + break; + } + } else if (expression_pattern == RGBA_PARAMETER && line[i] == ')' && end < 0) { + end = i + 1; + + break; + } else if (expression_pattern == NOT_PARSED && line[i] == '.') { begin = i; + expression_pattern = COLOR_NAME; + } else if (expression_pattern == COLOR_NAME && end < 0 && (line[i] == ' ' || line[i] == '\t')) { + // Including '.' and spaces. continue; - } else if (line[i] == ')') { - end = i + 1; - valid = true; + } else if (expression_pattern == COLOR_NAME && !(line[i] == '_' || ('A' <= line[i] && line[i] <= 'Z'))) { + end = i; + break; } } - if (valid) { - color_args = line.substr(begin, end - begin); - String stripped = color_args.replace(" ", "").replace("(", "").replace(")", ""); - Vector<float> color = stripped.split_floats(","); - if (color.size() > 2) { - float alpha = color.size() > 3 ? color[3] : 1.0f; - color_picker->set_pick_color(Color(color[0], color[1], color[2], alpha)); - } + + switch (expression_pattern) { + case RGBA_PARAMETER: { + color_args = line.substr(begin, end - begin); + String stripped = color_args.replace(" ", "").replace("\t", "").replace("(", "").replace(")", ""); + PackedFloat64Array color = stripped.split_floats(","); + if (color.size() > 2) { + float alpha = color.size() > 3 ? color[3] : 1.0f; + color_picker->set_pick_color(Color(color[0], color[1], color[2], alpha)); + } + } break; + case COLOR_NAME: { + if (end < 0) { + end = line.length(); + } + color_args = line.substr(begin, end - begin); + const String color_name = color_args.replace(" ", "").replace("\t", "").replace(".", ""); + const int color_index = Color::find_named_color(color_name); + if (0 <= color_index) { + const Color color_constant = Color::get_named_color(color_index); + color_picker->set_pick_color(color_constant); + } else { + has_color = false; + } + } break; + default: + has_color = false; + break; + } + if (has_color) { color_panel->set_position(get_screen_position() + local_pos); - } else { - has_color = false; } } _make_context_menu(tx->has_selection(), has_color, foldable, open_docs, goto_definition, local_pos); @@ -1635,7 +1932,7 @@ void ScriptTextEditor::_color_changed(const Color &p_color) { code_editor->get_text_editor()->begin_complex_operation(); code_editor->get_text_editor()->set_line(color_position.x, line_with_replaced_args); code_editor->get_text_editor()->end_complex_operation(); - code_editor->get_text_editor()->update(); + code_editor->get_text_editor()->queue_redraw(); } void ScriptTextEditor::_prepare_edit_menu() { @@ -1659,8 +1956,8 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL); context_menu->add_separator(); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_left"), EDIT_INDENT_LEFT); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); @@ -1698,7 +1995,7 @@ void ScriptTextEditor::_enable_code_editor() { VSplitContainer *editor_box = memnew(VSplitContainer); add_child(editor_box); - editor_box->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + editor_box->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); editor_box->set_v_size_flags(SIZE_EXPAND_FILL); editor_box->add_child(code_editor); @@ -1737,20 +2034,10 @@ void ScriptTextEditor::_enable_code_editor() { color_picker = memnew(ColorPicker); color_picker->set_deferred_mode(true); color_picker->connect("color_changed", callable_mp(this, &ScriptTextEditor::_color_changed)); + color_panel->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(color_picker)); color_panel->add_child(color_picker); - // get default color picker mode from editor settings - int default_color_mode = EDITOR_GET("interface/inspector/default_color_picker_mode"); - if (default_color_mode == 1) { - color_picker->set_hsv_mode(true); - } else if (default_color_mode == 2) { - color_picker->set_raw_mode(true); - } - - int picker_shape = EDITOR_GET("interface/inspector/default_color_picker_shape"); - color_picker->set_picker_shape((ColorPicker::PickerShapeType)picker_shape); - quick_open = memnew(ScriptEditorQuickOpen); quick_open->connect("goto_line", callable_mp(this, &ScriptTextEditor::_goto_line)); add_child(quick_open); @@ -1760,17 +2047,6 @@ void ScriptTextEditor::_enable_code_editor() { add_child(connection_info_dialog); - edit_hb->add_child(search_menu); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); - search_menu->get_popup()->add_separator(); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES); - search_menu->get_popup()->add_separator(); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help"), HELP_CONTEXTUAL); - search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); - edit_hb->add_child(edit_menu); edit_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_prepare_edit_menu)); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); @@ -1781,41 +2057,75 @@ void ScriptTextEditor::_enable_code_editor() { edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE); edit_menu->get_popup()->add_separator(); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection"), EDIT_EVALUATE); edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_left"), EDIT_INDENT_LEFT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES); + { + PopupMenu *sub_menu = memnew(PopupMenu); + sub_menu->set_name("line_menu"); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); + sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); + edit_menu->get_popup()->add_child(sub_menu); + edit_menu->get_popup()->add_submenu_item(TTR("Line"), "line_menu"); + } + { + PopupMenu *sub_menu = memnew(PopupMenu); + sub_menu->set_name("folding_menu"); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES); + sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); + edit_menu->get_popup()->add_child(sub_menu); + edit_menu->get_popup()->add_submenu_item(TTR("Folding"), "folding_menu"); + } edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection"), EDIT_EVALUATE); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs"), EDIT_CONVERT_INDENT_TO_TABS); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/auto_indent"), EDIT_AUTO_INDENT); + { + PopupMenu *sub_menu = memnew(PopupMenu); + sub_menu->set_name("indent_menu"); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs"), EDIT_CONVERT_INDENT_TO_TABS); + sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/auto_indent"), EDIT_AUTO_INDENT); + sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); + edit_menu->get_popup()->add_child(sub_menu); + edit_menu->get_popup()->add_submenu_item(TTR("Indentation"), "indent_menu"); + } edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); edit_menu->get_popup()->add_separator(); - - edit_menu->get_popup()->add_child(convert_case); - edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "convert_case"); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KeyModifierMask::SHIFT | Key::F4), EDIT_TO_UPPERCASE); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase"), KeyModifierMask::SHIFT | Key::F5), EDIT_TO_LOWERCASE); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KeyModifierMask::SHIFT | Key::F6), EDIT_CAPITALIZE); - convert_case->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); - + { + PopupMenu *sub_menu = memnew(PopupMenu); + sub_menu->set_name("convert_case"); + sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KeyModifierMask::SHIFT | Key::F4), EDIT_TO_UPPERCASE); + sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase"), KeyModifierMask::SHIFT | Key::F5), EDIT_TO_LOWERCASE); + sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KeyModifierMask::SHIFT | Key::F6), EDIT_CAPITALIZE); + sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); + edit_menu->get_popup()->add_child(sub_menu); + edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "convert_case"); + } edit_menu->get_popup()->add_child(highlighter_menu); edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter"), "highlighter_menu"); highlighter_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_change_syntax_highlighter)); + edit_hb->add_child(search_menu); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); + search_menu->get_popup()->add_separator(); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES); + search_menu->get_popup()->add_separator(); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help"), HELP_CONTEXTUAL); + search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); + _load_theme_settings(); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES); edit_hb->add_child(goto_menu); goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function"), SEARCH_LOCATE_FUNCTION); goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE); @@ -1839,7 +2149,7 @@ void ScriptTextEditor::_enable_code_editor() { ScriptTextEditor::ScriptTextEditor() { code_editor = memnew(CodeTextEditor); code_editor->add_theme_constant_override("separation", 2); - code_editor->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + code_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); code_editor->set_code_complete_func(_code_complete_scripts, this); code_editor->set_v_size_flags(SIZE_EXPAND_FILL); @@ -1872,7 +2182,7 @@ ScriptTextEditor::ScriptTextEditor() { update_settings(); - code_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line")); + code_editor->get_text_editor()->set_code_hint_draw_below(EDITOR_GET("text_editor/completion/put_callhint_tooltip_below_current_line")); code_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true); code_editor->get_text_editor()->set_context_menu_enabled(false); @@ -1888,9 +2198,6 @@ ScriptTextEditor::ScriptTextEditor() { edit_menu->set_switch_on_hover(true); edit_menu->set_shortcut_context(this); - convert_case = memnew(PopupMenu); - convert_case->set_name("convert_case"); - highlighter_menu = memnew(PopupMenu); highlighter_menu->set_name("highlighter_menu"); @@ -1921,7 +2228,7 @@ ScriptTextEditor::ScriptTextEditor() { connection_info_dialog = memnew(ConnectionInfoDialog); - code_editor->get_text_editor()->set_drag_forwarding(this); + SET_DRAG_FORWARDING_GCD(code_editor->get_text_editor(), ScriptTextEditor); } ScriptTextEditor::~ScriptTextEditor() { @@ -1935,7 +2242,6 @@ ScriptTextEditor::~ScriptTextEditor() { memdelete(color_panel); memdelete(edit_hb); memdelete(edit_menu); - memdelete(convert_case); memdelete(highlighter_menu); memdelete(search_menu); memdelete(goto_menu); @@ -1945,7 +2251,7 @@ ScriptTextEditor::~ScriptTextEditor() { } } -static ScriptEditorBase *create_editor(const RES &p_resource) { +static ScriptEditorBase *create_editor(const Ref<Resource> &p_resource) { if (Object::cast_to<Script>(*p_resource)) { return memnew(ScriptTextEditor); } @@ -1955,58 +2261,58 @@ static ScriptEditorBase *create_editor(const RES &p_resource) { void ScriptTextEditor::register_editor() { ED_SHORTCUT("script_text_editor/move_up", TTR("Move Up"), KeyModifierMask::ALT | Key::UP); ED_SHORTCUT("script_text_editor/move_down", TTR("Move Down"), KeyModifierMask::ALT | Key::DOWN); - ED_SHORTCUT("script_text_editor/delete_line", TTR("Delete Line"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::K); + ED_SHORTCUT("script_text_editor/delete_line", TTR("Delete Line"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::K); // Leave these at zero, same can be accomplished with tab/shift-tab, including selection. // The next/previous in history shortcut in this case makes a lot more sense. - ED_SHORTCUT("script_text_editor/indent_left", TTR("Indent Left"), Key::NONE); - ED_SHORTCUT("script_text_editor/indent_right", TTR("Indent Right"), Key::NONE); - ED_SHORTCUT("script_text_editor/toggle_comment", TTR("Toggle Comment"), KeyModifierMask::CMD | Key::K); + ED_SHORTCUT("script_text_editor/indent", TTR("Indent"), Key::NONE); + ED_SHORTCUT("script_text_editor/unindent", TTR("Unindent"), KeyModifierMask::SHIFT | Key::TAB); + ED_SHORTCUT("script_text_editor/toggle_comment", TTR("Toggle Comment"), KeyModifierMask::CMD_OR_CTRL | Key::K); ED_SHORTCUT("script_text_editor/toggle_fold_line", TTR("Fold/Unfold Line"), KeyModifierMask::ALT | Key::F); ED_SHORTCUT("script_text_editor/fold_all_lines", TTR("Fold All Lines"), Key::NONE); ED_SHORTCUT("script_text_editor/unfold_all_lines", TTR("Unfold All Lines"), Key::NONE); - ED_SHORTCUT("script_text_editor/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::SHIFT | KeyModifierMask::CMD | Key::D); - ED_SHORTCUT_OVERRIDE("script_text_editor/duplicate_selection", "macos", KeyModifierMask::SHIFT | KeyModifierMask::CMD | Key::C); - ED_SHORTCUT("script_text_editor/evaluate_selection", TTR("Evaluate Selection"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::E); - ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KeyModifierMask::CMD | KeyModifierMask::ALT | Key::T); - ED_SHORTCUT("script_text_editor/convert_indent_to_spaces", TTR("Convert Indent to Spaces"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::Y); - ED_SHORTCUT("script_text_editor/convert_indent_to_tabs", TTR("Convert Indent to Tabs"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::I); - ED_SHORTCUT("script_text_editor/auto_indent", TTR("Auto Indent"), KeyModifierMask::CMD | Key::I); + ED_SHORTCUT("script_text_editor/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::SHIFT | KeyModifierMask::CTRL | Key::D); + ED_SHORTCUT_OVERRIDE("script_text_editor/duplicate_selection", "macos", KeyModifierMask::SHIFT | KeyModifierMask::META | Key::C); + ED_SHORTCUT("script_text_editor/evaluate_selection", TTR("Evaluate Selection"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::E); + ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::T); + ED_SHORTCUT("script_text_editor/convert_indent_to_spaces", TTR("Convert Indent to Spaces"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::Y); + ED_SHORTCUT("script_text_editor/convert_indent_to_tabs", TTR("Convert Indent to Tabs"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::I); + ED_SHORTCUT("script_text_editor/auto_indent", TTR("Auto Indent"), KeyModifierMask::CMD_OR_CTRL | Key::I); - ED_SHORTCUT_AND_COMMAND("script_text_editor/find", TTR("Find..."), KeyModifierMask::CMD | Key::F); + ED_SHORTCUT_AND_COMMAND("script_text_editor/find", TTR("Find..."), KeyModifierMask::CMD_OR_CTRL | Key::F); ED_SHORTCUT("script_text_editor/find_next", TTR("Find Next"), Key::F3); - ED_SHORTCUT_OVERRIDE("script_text_editor/find_next", "macos", KeyModifierMask::CMD | Key::G); + ED_SHORTCUT_OVERRIDE("script_text_editor/find_next", "macos", KeyModifierMask::META | Key::G); ED_SHORTCUT("script_text_editor/find_previous", TTR("Find Previous"), KeyModifierMask::SHIFT | Key::F3); - ED_SHORTCUT_OVERRIDE("script_text_editor/find_previous", "macos", KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::G); + ED_SHORTCUT_OVERRIDE("script_text_editor/find_previous", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::G); - ED_SHORTCUT_AND_COMMAND("script_text_editor/replace", TTR("Replace..."), KeyModifierMask::CMD | Key::R); - ED_SHORTCUT_OVERRIDE("script_text_editor/replace", "macos", KeyModifierMask::ALT | KeyModifierMask::CMD | Key::F); + ED_SHORTCUT_AND_COMMAND("script_text_editor/replace", TTR("Replace..."), KeyModifierMask::CTRL | Key::R); + ED_SHORTCUT_OVERRIDE("script_text_editor/replace", "macos", KeyModifierMask::ALT | KeyModifierMask::META | Key::F); - ED_SHORTCUT("script_text_editor/find_in_files", TTR("Find in Files..."), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::F); - ED_SHORTCUT("script_text_editor/replace_in_files", TTR("Replace in Files..."), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::R); + ED_SHORTCUT("script_text_editor/find_in_files", TTR("Find in Files..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F); + ED_SHORTCUT("script_text_editor/replace_in_files", TTR("Replace in Files..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R); ED_SHORTCUT("script_text_editor/contextual_help", TTR("Contextual Help"), KeyModifierMask::ALT | Key::F1); ED_SHORTCUT_OVERRIDE("script_text_editor/contextual_help", "macos", KeyModifierMask::ALT | KeyModifierMask::SHIFT | Key::SPACE); - ED_SHORTCUT("script_text_editor/toggle_bookmark", TTR("Toggle Bookmark"), KeyModifierMask::CMD | KeyModifierMask::ALT | Key::B); - ED_SHORTCUT("script_text_editor/goto_next_bookmark", TTR("Go to Next Bookmark"), KeyModifierMask::CMD | Key::B); - ED_SHORTCUT("script_text_editor/goto_previous_bookmark", TTR("Go to Previous Bookmark"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::B); + ED_SHORTCUT("script_text_editor/toggle_bookmark", TTR("Toggle Bookmark"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::B); + ED_SHORTCUT("script_text_editor/goto_next_bookmark", TTR("Go to Next Bookmark"), KeyModifierMask::CMD_OR_CTRL | Key::B); + ED_SHORTCUT("script_text_editor/goto_previous_bookmark", TTR("Go to Previous Bookmark"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::B); ED_SHORTCUT("script_text_editor/remove_all_bookmarks", TTR("Remove All Bookmarks"), Key::NONE); - ED_SHORTCUT("script_text_editor/goto_function", TTR("Go to Function..."), KeyModifierMask::ALT | KeyModifierMask::CMD | Key::F); - ED_SHORTCUT_OVERRIDE("script_text_editor/goto_function", "macos", KeyModifierMask::CTRL | KeyModifierMask::CMD | Key::J); + ED_SHORTCUT("script_text_editor/goto_function", TTR("Go to Function..."), KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::F); + ED_SHORTCUT_OVERRIDE("script_text_editor/goto_function", "macos", KeyModifierMask::CTRL | KeyModifierMask::META | Key::J); - ED_SHORTCUT("script_text_editor/goto_line", TTR("Go to Line..."), KeyModifierMask::CMD | Key::L); + ED_SHORTCUT("script_text_editor/goto_line", TTR("Go to Line..."), KeyModifierMask::CMD_OR_CTRL | Key::L); ED_SHORTCUT("script_text_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), Key::F9); - ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_breakpoint", "macos", KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::B); + ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_breakpoint", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::B); - ED_SHORTCUT("script_text_editor/remove_all_breakpoints", TTR("Remove All Breakpoints"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::F9); - ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Go to Next Breakpoint"), KeyModifierMask::CMD | Key::PERIOD); - ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Go to Previous Breakpoint"), KeyModifierMask::CMD | Key::COMMA); + ED_SHORTCUT("script_text_editor/remove_all_breakpoints", TTR("Remove All Breakpoints"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F9); + ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Go to Next Breakpoint"), KeyModifierMask::CMD_OR_CTRL | Key::PERIOD); + ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Go to Previous Breakpoint"), KeyModifierMask::CMD_OR_CTRL | Key::COMMA); ScriptEditor::register_create_script_editor_function(create_editor); } diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index 6e67444489..1d96376748 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -1,40 +1,44 @@ -/*************************************************************************/ -/* script_text_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* script_text_editor.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 SCRIPT_TEXT_EDITOR_H #define SCRIPT_TEXT_EDITOR_H +#include "script_editor_plugin.h" + +#include "editor/code_editor.h" #include "scene/gui/color_picker.h" #include "scene/gui/dialogs.h" #include "scene/gui/tree.h" -#include "script_editor_plugin.h" + +class RichTextLabel; class ConnectionInfoDialog : public AcceptDialog { GDCLASS(ConnectionInfoDialog, AcceptDialog); @@ -62,6 +66,9 @@ class ScriptTextEditor : public ScriptEditorBase { bool editor_enabled = false; Vector<String> functions; + List<ScriptLanguage::Warning> warnings; + List<ScriptLanguage::ScriptError> errors; + HashSet<int> safe_lines; List<Connection> missing_connections; @@ -76,7 +83,6 @@ class ScriptTextEditor : public ScriptEditorBase { PopupMenu *breakpoints_menu = nullptr; PopupMenu *highlighter_menu = nullptr; PopupMenu *context_menu = nullptr; - PopupMenu *convert_case = nullptr; GotoLineDialog *goto_line_dialog = nullptr; ScriptEditorQuickOpen *quick_open = nullptr; @@ -114,8 +120,8 @@ class ScriptTextEditor : public ScriptEditorBase { EDIT_TOGGLE_COMMENT, EDIT_MOVE_LINE_UP, EDIT_MOVE_LINE_DOWN, - EDIT_INDENT_RIGHT, - EDIT_INDENT_LEFT, + EDIT_INDENT, + EDIT_UNINDENT, EDIT_DELETE_LINE, EDIT_DUPLICATE_SELECTION, EDIT_PICK_COLOR, @@ -154,11 +160,13 @@ protected: void _breakpoint_toggled(int p_row); void _validate_script(); // No longer virtual. + void _update_warnings(); + void _update_errors(); void _update_bookmark_list(); void _bookmark_item_pressed(int p_idx); - static void _code_complete_scripts(void *p_ud, const String &p_code, List<ScriptCodeCompletionOption> *r_options, bool &r_force); - void _code_complete_script(const String &p_code, List<ScriptCodeCompletionOption> *r_options, bool &r_force); + static void _code_complete_scripts(void *p_ud, const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force); + void _code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force); void _load_theme_settings(); void _set_theme_for_script(); @@ -168,9 +176,8 @@ protected: void _warning_clicked(Variant p_line); void _notification(int p_what); - static void _bind_methods(); - Map<String, Ref<EditorSyntaxHighlighter>> highlighters; + HashMap<String, Ref<EditorSyntaxHighlighter>> highlighters; void _change_syntax_highlighter(int p_idx); void _edit_option(int p_op); @@ -200,9 +207,9 @@ public: void update_toggle_scripts_button() override; virtual void apply_code() override; - virtual RES get_edited_resource() const override; - virtual void set_edited_resource(const RES &p_res) override; - virtual void enable_editor() override; + virtual Ref<Resource> get_edited_resource() const override; + virtual void set_edited_resource(const Ref<Resource> &p_res) override; + virtual void enable_editor(Control *p_shortcut_context = nullptr) override; virtual Vector<String> get_functions() override; virtual void reload_text() override; virtual String get_name() override; @@ -210,6 +217,7 @@ public: virtual bool is_unsaved() override; virtual Variant get_edit_state() override; virtual void set_edit_state(const Variant &p_state) override; + virtual Variant get_navigation_state() override; virtual void ensure_focus() override; virtual void trim_trailing_whitespace() override; virtual void insert_final_newline() override; @@ -224,7 +232,7 @@ public: virtual void clear_executing_line() override; virtual void reload(bool p_soft) override; - virtual Array get_breakpoints() override; + virtual PackedInt32Array get_breakpoints() override; virtual void set_breakpoint(int p_line, bool p_enabled) override; virtual void clear_breakpoints() override; @@ -233,7 +241,7 @@ public: virtual bool show_members_overview() override; - virtual void set_tooltip_request_func(String p_method, Object *p_obj) override; + virtual void set_tooltip_request_func(const Callable &p_toolip_callback) override; virtual void set_debugger_active(bool p_active) override; @@ -251,4 +259,51 @@ public: ~ScriptTextEditor(); }; +const int KIND_COUNT = 10; +// The order in which to sort code completion options. +const ScriptLanguage::CodeCompletionKind KIND_SORT_ORDER[KIND_COUNT] = { + ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, + ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, + ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, + ScriptLanguage::CODE_COMPLETION_KIND_ENUM, + ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, + ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, + ScriptLanguage::CODE_COMPLETION_KIND_CLASS, + ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH, + ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH, + ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT, +}; + +// The custom comparer which will sort completion options. +struct CodeCompletionOptionCompare { + _FORCE_INLINE_ bool operator()(const ScriptLanguage::CodeCompletionOption &l, const ScriptLanguage::CodeCompletionOption &r) const { + if (l.location == r.location) { + // If locations are same, sort on kind + if (l.kind == r.kind) { + // If kinds are same, sort alphanumeric + return l.display < r.display; + } + + // Sort kinds based on the const sorting array defined above. Lower index = higher priority. + int l_index = -1; + int r_index = -1; + for (int i = 0; i < KIND_COUNT; i++) { + const ScriptLanguage::CodeCompletionKind kind = KIND_SORT_ORDER[i]; + l_index = kind == l.kind ? i : l_index; + r_index = kind == r.kind ? i : r_index; + + if (l_index != -1 && r_index != -1) { + return l_index < r_index; + } + } + + // This return should never be hit unless something goes wrong. + // l and r should always have a Kind which is in the sort order array. + return l.display < r.display; + } + + return l.location < r.location; + } +}; + #endif // SCRIPT_TEXT_EDITOR_H diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index e8bbeb0834..907bc81674 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -1,872 +1,476 @@ -/*************************************************************************/ -/* shader_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* shader_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 "shader_editor_plugin.h" -#include "core/io/resource_loader.h" -#include "core/io/resource_saver.h" -#include "core/os/keyboard.h" -#include "core/os/os.h" -#include "core/version_generated.gen.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" -#include "editor/editor_settings.h" -#include "editor/project_settings_editor.h" -#include "editor/property_editor.h" -#include "servers/display_server.h" -#include "servers/rendering/shader_types.h" - -/*** SHADER SCRIPT EDITOR ****/ - -static bool saved_warnings_enabled = false; -static bool saved_treat_warning_as_errors = false; -static Map<ShaderWarning::Code, bool> saved_warnings; -static uint32_t saved_warning_flags = 0U; - -Ref<Shader> ShaderTextEditor::get_edited_shader() const { - return shader; -} - -void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) { - if (shader == p_shader) { - return; - } - shader = p_shader; - - _load_theme_settings(); - - get_text_editor()->set_text(p_shader->get_code()); - get_text_editor()->clear_undo_history(); - get_text_editor()->call_deferred(SNAME("set_h_scroll"), 0); - get_text_editor()->call_deferred(SNAME("set_v_scroll"), 0); - - _validate_script(); - _line_col_changed(); -} - -void ShaderTextEditor::reload_text() { - ERR_FAIL_COND(shader.is_null()); - - CodeEdit *te = get_text_editor(); - int column = te->get_caret_column(); - int row = te->get_caret_line(); - int h = te->get_h_scroll(); - int v = te->get_v_scroll(); - - te->set_text(shader->get_code()); - te->set_caret_line(row); - te->set_caret_column(column); - te->set_h_scroll(h); - te->set_v_scroll(v); - - te->tag_saved_version(); - - update_line_and_column(); -} - -void ShaderTextEditor::set_warnings_panel(RichTextLabel *p_warnings_panel) { - warnings_panel = p_warnings_panel; -} +#include "editor/editor_undo_redo_manager.h" +#include "editor/filesystem_dock.h" +#include "editor/inspector_dock.h" +#include "editor/plugins/text_shader_editor.h" +#include "editor/plugins/visual_shader_editor_plugin.h" +#include "editor/shader_create_dialog.h" +#include "scene/gui/item_list.h" +#include "scene/gui/texture_rect.h" + +void ShaderEditorPlugin::_update_shader_list() { + shader_list->clear(); + for (EditedShader &edited_shader : edited_shaders) { + Ref<Resource> shader = edited_shader.shader; + if (shader.is_null()) { + shader = edited_shader.shader_inc; + } -void ShaderTextEditor::_load_theme_settings() { - CodeEdit *text_editor = get_text_editor(); - Color updated_marked_line_color = EDITOR_GET("text_editor/theme/highlighting/mark_color"); - if (updated_marked_line_color != marked_line_color) { - for (int i = 0; i < text_editor->get_line_count(); i++) { - if (text_editor->get_line_background_color(i) == marked_line_color) { - text_editor->set_line_background_color(i, updated_marked_line_color); + String path = shader->get_path(); + String text = path.get_file(); + if (text.is_empty()) { + // This appears for newly created built-in shaders before saving the scene. + text = TTR("[unsaved]"); + } else if (shader->is_built_in()) { + const String &shader_name = shader->get_name(); + if (!shader_name.is_empty()) { + text = vformat("%s (%s)", shader_name, text.get_slice("::", 0)); } } - marked_line_color = updated_marked_line_color; - } - syntax_highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color")); - syntax_highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color")); - syntax_highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/function_color")); - syntax_highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/member_variable_color")); - - syntax_highlighter->clear_keyword_colors(); - - List<String> keywords; - ShaderLanguage::get_keyword_list(&keywords); - const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); - const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); - - for (const String &E : keywords) { - if (ShaderLanguage::is_control_flow_keyword(E)) { - syntax_highlighter->add_keyword_color(E, control_flow_keyword_color); - } else { - syntax_highlighter->add_keyword_color(E, keyword_color); + bool unsaved = false; + if (edited_shader.shader_editor) { + unsaved = edited_shader.shader_editor->is_unsaved(); } - } - - // Colorize built-ins like `COLOR` differently to make them easier - // to distinguish from keywords at a quick glance. + // TODO: Handle visual shaders too. - List<String> built_ins; - if (shader.is_valid()) { - for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()))) { - for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) { - built_ins.push_back(F.key); - } + if (unsaved) { + text += "(*)"; } - const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); - - for (int i = 0; i < modes.size(); i++) { - const ShaderLanguage::ModeInfo &info = modes[i]; - - if (!info.options.is_empty()) { - for (int j = 0; j < info.options.size(); j++) { - built_ins.push_back(String(info.name) + "_" + String(info.options[j])); - } - } else { - built_ins.push_back(String(info.name)); - } + String _class = shader->get_class(); + if (!shader_list->has_theme_icon(_class, SNAME("EditorIcons"))) { + _class = "TextFile"; } - } + Ref<Texture2D> icon = shader_list->get_theme_icon(_class, SNAME("EditorIcons")); - const Color user_type_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color"); + shader_list->add_item(text, icon); + shader_list->set_item_tooltip(shader_list->get_item_count() - 1, path); + } - for (const String &E : built_ins) { - syntax_highlighter->add_keyword_color(E, user_type_color); + if (shader_tabs->get_tab_count()) { + shader_list->select(shader_tabs->get_current_tab()); } - // Colorize comments. - const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); - syntax_highlighter->clear_color_regions(); - syntax_highlighter->add_color_region("/*", "*/", comment_color, false); - syntax_highlighter->add_color_region("//", "", comment_color, true); + for (int i = FILE_SAVE; i < FILE_MAX; i++) { + file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), edited_shaders.is_empty()); + } - text_editor->clear_comment_delimiters(); - text_editor->add_comment_delimiter("/*", "*/", false); - text_editor->add_comment_delimiter("//", "", true); + _update_shader_list_status(); +} - if (!text_editor->has_auto_brace_completion_open_key("/*")) { - text_editor->add_auto_brace_completion_pair("/*", "*/"); +void ShaderEditorPlugin::_update_shader_list_status() { + for (int i = 0; i < shader_list->get_item_count(); i++) { + TextShaderEditor *se = Object::cast_to<TextShaderEditor>(shader_tabs->get_tab_control(i)); + if (se) { + if (se->was_compilation_successful()) { + shader_list->set_item_tag_icon(i, Ref<Texture2D>()); + } else { + shader_list->set_item_tag_icon(i, shader_list->get_theme_icon(SNAME("Error"), SNAME("EditorIcons"))); + } + } } +} - if (warnings_panel) { - // Warnings panel - warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts"))); - warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts"))); +void ShaderEditorPlugin::_move_shader_tab(int p_from, int p_to) { + if (p_from == p_to) { + return; } + EditedShader es = edited_shaders[p_from]; + edited_shaders.remove_at(p_from); + edited_shaders.insert(p_to, es); + shader_tabs->move_child(shader_tabs->get_tab_control(p_from), p_to); + _update_shader_list(); } -void ShaderTextEditor::_check_shader_mode() { - String type = ShaderLanguage::get_shader_type(get_text_editor()->get_text()); +void ShaderEditorPlugin::edit(Object *p_object) { + if (!p_object) { + return; + } - Shader::Mode mode; + EditedShader es; - if (type == "canvas_item") { - mode = Shader::MODE_CANVAS_ITEM; - } else if (type == "particles") { - mode = Shader::MODE_PARTICLES; - } else if (type == "sky") { - mode = Shader::MODE_SKY; - } else if (type == "fog") { - mode = Shader::MODE_FOG; + ShaderInclude *si = Object::cast_to<ShaderInclude>(p_object); + if (si != nullptr) { + for (uint32_t i = 0; i < edited_shaders.size(); i++) { + if (edited_shaders[i].shader_inc.ptr() == si) { + shader_tabs->set_current_tab(i); + shader_list->select(i); + return; + } + } + es.shader_inc = Ref<ShaderInclude>(si); + es.shader_editor = memnew(TextShaderEditor); + es.shader_editor->edit(si); + shader_tabs->add_child(es.shader_editor); + es.shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list)); } else { - mode = Shader::MODE_SPATIAL; + Shader *s = Object::cast_to<Shader>(p_object); + for (uint32_t i = 0; i < edited_shaders.size(); i++) { + if (edited_shaders[i].shader.ptr() == s) { + shader_tabs->set_current_tab(i); + shader_list->select(i); + return; + } + } + es.shader = Ref<Shader>(s); + Ref<VisualShader> vs = es.shader; + if (vs.is_valid()) { + es.visual_shader_editor = memnew(VisualShaderEditor); + shader_tabs->add_child(es.visual_shader_editor); + es.visual_shader_editor->edit(vs.ptr()); + } else { + es.shader_editor = memnew(TextShaderEditor); + shader_tabs->add_child(es.shader_editor); + es.shader_editor->edit(s); + es.shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list)); + } } - if (shader->get_mode() != mode) { - shader->set_code(get_text_editor()->get_text()); - _load_theme_settings(); - } + shader_tabs->set_current_tab(shader_tabs->get_tab_count() - 1); + edited_shaders.push_back(es); + _update_shader_list(); } -static ShaderLanguage::DataType _get_global_variable_type(const StringName &p_variable) { - RS::GlobalVariableType gvt = RS::get_singleton()->global_variable_get_type(p_variable); - return RS::global_variable_type_get_shader_datatype(gvt); +bool ShaderEditorPlugin::handles(Object *p_object) const { + return Object::cast_to<Shader>(p_object) != nullptr || Object::cast_to<ShaderInclude>(p_object) != nullptr; } -void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptCodeCompletionOption> *r_options) { - _check_shader_mode(); - - ShaderLanguage sl; - String calltip; - - ShaderLanguage::ShaderCompileInfo info; - info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())); - info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); - info.shader_types = ShaderTypes::get_singleton()->get_types(); - info.global_variable_type_func = _get_global_variable_type; - - sl.complete(p_code, info, r_options, calltip); - - get_text_editor()->set_code_hint(calltip); +void ShaderEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + EditorNode::get_singleton()->make_bottom_panel_item_visible(main_split); + } } -void ShaderTextEditor::_validate_script() { - _check_shader_mode(); - - String code = get_text_editor()->get_text(); - //List<StringName> params; - //shader->get_param_list(¶ms); - - ShaderLanguage::ShaderCompileInfo info; - info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())); - info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); - info.shader_types = ShaderTypes::get_singleton()->get_types(); - info.global_variable_type_func = _get_global_variable_type; - - ShaderLanguage sl; - - sl.enable_warning_checking(saved_warnings_enabled); - sl.set_warning_flags(saved_warning_flags); - - Error err = sl.compile(code, info); +void ShaderEditorPlugin::selected_notify() { +} - if (err != OK) { - String error_text = "error(" + itos(sl.get_error_line()) + "): " + sl.get_error_text(); - set_error(error_text); - set_error_pos(sl.get_error_line() - 1, 0); - for (int i = 0; i < get_text_editor()->get_line_count(); i++) { - get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); +TextShaderEditor *ShaderEditorPlugin::get_shader_editor(const Ref<Shader> &p_for_shader) { + for (EditedShader &edited_shader : edited_shaders) { + if (edited_shader.shader == p_for_shader) { + return edited_shader.shader_editor; } - get_text_editor()->set_line_background_color(sl.get_error_line() - 1, marked_line_color); - } else { - for (int i = 0; i < get_text_editor()->get_line_count(); i++) { - get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); - } - set_error(""); } + return nullptr; +} - if (warnings.size() > 0 || err != OK) { - warnings_panel->clear(); - } - warnings.clear(); - for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) { - warnings.push_back(E->get()); - } - if (warnings.size() > 0 && err == OK) { - warnings.sort_custom<WarningsComparator>(); - _update_warning_panel(); - } else { - set_warning_count(0); +VisualShaderEditor *ShaderEditorPlugin::get_visual_shader_editor(const Ref<Shader> &p_for_shader) { + for (EditedShader &edited_shader : edited_shaders) { + if (edited_shader.shader == p_for_shader) { + return edited_shader.visual_shader_editor; + } } - emit_signal(SNAME("script_changed")); + return nullptr; } -void ShaderTextEditor::_update_warning_panel() { - int warning_count = 0; - - warnings_panel->push_table(2); - for (int i = 0; i < warnings.size(); i++) { - ShaderWarning &w = warnings[i]; - - if (warning_count == 0) { - if (saved_treat_warning_as_errors) { - String error_text = "error(" + itos(w.get_line()) + "): " + w.get_message() + " " + TTR("Warnings should be fixed to prevent errors."); - set_error_pos(w.get_line() - 1, 0); - set_error(error_text); - get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color); - } +void ShaderEditorPlugin::save_external_data() { + for (EditedShader &edited_shader : edited_shaders) { + if (edited_shader.shader_editor) { + edited_shader.shader_editor->save_external_data(); } + } + _update_shader_list(); +} - warning_count++; - int line = w.get_line(); - - // First cell. - warnings_panel->push_cell(); - warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), SNAME("Editor"))); - if (line != -1) { - warnings_panel->push_meta(line - 1); - warnings_panel->add_text(TTR("Line") + " " + itos(line)); - warnings_panel->add_text(" (" + w.get_name() + "):"); - warnings_panel->pop(); // Meta goto. - } else { - warnings_panel->add_text(w.get_name() + ":"); +void ShaderEditorPlugin::apply_changes() { + for (EditedShader &edited_shader : edited_shaders) { + if (edited_shader.shader_editor) { + edited_shader.shader_editor->apply_shaders(); } - warnings_panel->pop(); // Color. - warnings_panel->pop(); // Cell. - - // Second cell. - warnings_panel->push_cell(); - warnings_panel->add_text(w.get_message()); - warnings_panel->pop(); // Cell. } - warnings_panel->pop(); // Table. +} - set_warning_count(warning_count); +void ShaderEditorPlugin::_shader_selected(int p_index) { + if (edited_shaders[p_index].shader_editor) { + edited_shaders[p_index].shader_editor->validate_script(); + } + shader_tabs->set_current_tab(p_index); + shader_list->select(p_index); } -void ShaderTextEditor::_bind_methods() { +void ShaderEditorPlugin::_shader_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index) { + if (p_mouse_button_index == MouseButton::MIDDLE) { + _close_shader(p_item); + } } -ShaderTextEditor::ShaderTextEditor() { - syntax_highlighter.instantiate(); - get_text_editor()->set_syntax_highlighter(syntax_highlighter); +void ShaderEditorPlugin::_close_shader(int p_index) { + ERR_FAIL_INDEX(p_index, shader_tabs->get_tab_count()); + Control *c = shader_tabs->get_tab_control(p_index); + memdelete(c); + edited_shaders.remove_at(p_index); + _update_shader_list(); + EditorUndoRedoManager::get_singleton()->clear_history(); // To prevent undo on deleted graphs. } -/*** SCRIPT EDITOR ******/ +void ShaderEditorPlugin::_resource_saved(Object *obj) { + // May have been renamed on save. + for (EditedShader &edited_shader : edited_shaders) { + if (edited_shader.shader.ptr() == obj) { + _update_shader_list(); + return; + } + } +} -void ShaderEditor::_menu_option(int p_option) { - switch (p_option) { - case EDIT_UNDO: { - shader_editor->get_text_editor()->undo(); +void ShaderEditorPlugin::_menu_item_pressed(int p_index) { + switch (p_index) { + case FILE_NEW: { + String base_path = FileSystemDock::get_singleton()->get_current_path().get_base_dir(); + shader_create_dialog->config(base_path.path_join("new_shader"), false, false, 0); + shader_create_dialog->popup_centered(); } break; - case EDIT_REDO: { - shader_editor->get_text_editor()->redo(); + case FILE_NEW_INCLUDE: { + String base_path = FileSystemDock::get_singleton()->get_current_path().get_base_dir(); + shader_create_dialog->config(base_path.path_join("new_shader"), false, false, 2); + shader_create_dialog->popup_centered(); } break; - case EDIT_CUT: { - shader_editor->get_text_editor()->cut(); + case FILE_OPEN: { + InspectorDock::get_singleton()->open_resource("Shader"); } break; - case EDIT_COPY: { - shader_editor->get_text_editor()->copy(); + case FILE_OPEN_INCLUDE: { + InspectorDock::get_singleton()->open_resource("ShaderInclude"); } break; - case EDIT_PASTE: { - shader_editor->get_text_editor()->paste(); - } break; - case EDIT_SELECT_ALL: { - shader_editor->get_text_editor()->select_all(); - } break; - case EDIT_MOVE_LINE_UP: { - shader_editor->move_lines_up(); - } break; - case EDIT_MOVE_LINE_DOWN: { - shader_editor->move_lines_down(); - } break; - case EDIT_INDENT_LEFT: { - if (shader.is_null()) { - return; + case FILE_SAVE: { + int index = shader_tabs->get_current_tab(); + ERR_FAIL_INDEX(index, shader_tabs->get_tab_count()); + if (edited_shaders[index].shader.is_valid()) { + EditorNode::get_singleton()->save_resource(edited_shaders[index].shader); + } else { + EditorNode::get_singleton()->save_resource(edited_shaders[index].shader_inc); } - shader_editor->get_text_editor()->unindent_lines(); - } break; - case EDIT_INDENT_RIGHT: { - if (shader.is_null()) { - return; + if (edited_shaders[index].shader_editor) { + edited_shaders[index].shader_editor->tag_saved_version(); } - shader_editor->get_text_editor()->indent_lines(); - } break; - case EDIT_DELETE_LINE: { - shader_editor->delete_lines(); - } break; - case EDIT_DUPLICATE_SELECTION: { - shader_editor->duplicate_selection(); } break; - case EDIT_TOGGLE_COMMENT: { - if (shader.is_null()) { - return; + case FILE_SAVE_AS: { + int index = shader_tabs->get_current_tab(); + ERR_FAIL_INDEX(index, shader_tabs->get_tab_count()); + String path; + if (edited_shaders[index].shader.is_valid()) { + path = edited_shaders[index].shader->get_path(); + if (!path.is_resource_file()) { + path = ""; + } + EditorNode::get_singleton()->save_resource_as(edited_shaders[index].shader, path); + } else { + path = edited_shaders[index].shader_inc->get_path(); + if (!path.is_resource_file()) { + path = ""; + } + EditorNode::get_singleton()->save_resource_as(edited_shaders[index].shader_inc, path); + } + if (edited_shaders[index].shader_editor) { + edited_shaders[index].shader_editor->tag_saved_version(); } - - shader_editor->toggle_inline_comment("//"); - - } break; - case EDIT_COMPLETE: { - shader_editor->get_text_editor()->request_code_completion(); - } break; - case SEARCH_FIND: { - shader_editor->get_find_replace_bar()->popup_search(); - } break; - case SEARCH_FIND_NEXT: { - shader_editor->get_find_replace_bar()->search_next(); - } break; - case SEARCH_FIND_PREV: { - shader_editor->get_find_replace_bar()->search_prev(); - } break; - case SEARCH_REPLACE: { - shader_editor->get_find_replace_bar()->popup_replace(); - } break; - case SEARCH_GOTO_LINE: { - goto_line_dialog->popup_find_line(shader_editor->get_text_editor()); - } break; - case BOOKMARK_TOGGLE: { - shader_editor->toggle_bookmark(); - } break; - case BOOKMARK_GOTO_NEXT: { - shader_editor->goto_next_bookmark(); - } break; - case BOOKMARK_GOTO_PREV: { - shader_editor->goto_prev_bookmark(); } break; - case BOOKMARK_REMOVE_ALL: { - shader_editor->remove_all_bookmarks(); + case FILE_INSPECT: { + int index = shader_tabs->get_current_tab(); + ERR_FAIL_INDEX(index, shader_tabs->get_tab_count()); + if (edited_shaders[index].shader.is_valid()) { + EditorNode::get_singleton()->push_item(edited_shaders[index].shader.ptr()); + } else { + EditorNode::get_singleton()->push_item(edited_shaders[index].shader_inc.ptr()); + } } break; - case HELP_DOCS: { - OS::get_singleton()->shell_open(vformat("%s/tutorials/shaders/shader_reference/index.html", VERSION_DOCS_URL)); + case FILE_CLOSE: { + _close_shader(shader_tabs->get_current_tab()); } break; } - if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) { - shader_editor->get_text_editor()->call_deferred(SNAME("grab_focus")); - } } -void ShaderEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_WM_WINDOW_FOCUS_IN) { - _check_for_external_edit(); - } +void ShaderEditorPlugin::_shader_created(Ref<Shader> p_shader) { + EditorNode::get_singleton()->push_item(p_shader.ptr()); } -void ShaderEditor::_editor_settings_changed() { - shader_editor->update_editor_settings(); - - shader_editor->get_text_editor()->add_theme_constant_override("line_spacing", EditorSettings::get_singleton()->get("text_editor/appearance/whitespace/line_spacing")); - shader_editor->get_text_editor()->set_draw_breakpoints_gutter(false); - shader_editor->get_text_editor()->set_draw_executing_lines_gutter(false); +void ShaderEditorPlugin::_shader_include_created(Ref<ShaderInclude> p_shader_inc) { + EditorNode::get_singleton()->push_item(p_shader_inc.ptr()); } -void ShaderEditor::_show_warnings_panel(bool p_show) { - warnings_panel->set_visible(p_show); -} - -void ShaderEditor::_warning_clicked(Variant p_line) { - if (p_line.get_type() == Variant::INT) { - shader_editor->get_text_editor()->set_caret_line(p_line.operator int64_t()); +Variant ShaderEditorPlugin::get_drag_data_fw(const Point2 &p_point, Control *p_from) { + if (shader_list->get_item_count() == 0) { + return Variant(); } -} -void ShaderEditor::_bind_methods() { - ClassDB::bind_method("_show_warnings_panel", &ShaderEditor::_show_warnings_panel); - ClassDB::bind_method("_warning_clicked", &ShaderEditor::_warning_clicked); -} - -void ShaderEditor::ensure_select_current() { - /* - if (tab_container->get_child_count() && tab_container->get_current_tab()>=0) { - ShaderTextEditor *ste = Object::cast_to<ShaderTextEditor>(tab_container->get_child(tab_container->get_current_tab())); - if (!ste) - return; - Ref<Shader> shader = ste->get_edited_shader(); - get_scene()->get_root_node()->call("_resource_selected",shader); - }*/ -} - -void ShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) { - shader_editor->goto_line_selection(p_line, p_begin, p_end); -} - -void ShaderEditor::_project_settings_changed() { - _update_warnings(true); -} - -void ShaderEditor::_update_warnings(bool p_validate) { - bool changed = false; - - bool warnings_enabled = GLOBAL_GET("debug/shader_language/warnings/enable").booleanize(); - if (warnings_enabled != saved_warnings_enabled) { - saved_warnings_enabled = warnings_enabled; - changed = true; - } - - bool treat_warning_as_errors = GLOBAL_GET("debug/shader_language/warnings/treat_warnings_as_errors").booleanize(); - if (treat_warning_as_errors != saved_treat_warning_as_errors) { - saved_treat_warning_as_errors = treat_warning_as_errors; - changed = true; - } - - bool update_flags = false; - - for (int i = 0; i < ShaderWarning::WARNING_MAX; i++) { - ShaderWarning::Code code = (ShaderWarning::Code)i; - bool value = GLOBAL_GET("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code(code).to_lower()); - - if (saved_warnings[code] != value) { - saved_warnings[code] = value; - update_flags = true; - changed = true; - } - } - - if (update_flags) { - saved_warning_flags = (uint32_t)ShaderWarning::get_flags_from_codemap(saved_warnings); + int idx = shader_list->get_item_at_position(p_point); + if (idx < 0) { + return Variant(); } - if (p_validate && changed && shader_editor && shader_editor->get_edited_shader().is_valid()) { - shader_editor->validate_script(); - } -} + HBoxContainer *drag_preview = memnew(HBoxContainer); + String preview_name = shader_list->get_item_text(idx); + Ref<Texture2D> preview_icon = shader_list->get_item_icon(idx); -void ShaderEditor::_check_for_external_edit() { - if (shader.is_null() || !shader.is_valid()) { - return; + if (!preview_icon.is_null()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(preview_icon); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + drag_preview->add_child(tf); } + Label *label = memnew(Label(preview_name)); + drag_preview->add_child(label); + main_split->set_drag_preview(drag_preview); - if (shader->is_built_in()) { - return; - } + Dictionary drag_data; + drag_data["type"] = "shader_list_element"; + drag_data["shader_list_element"] = idx; - bool use_autoreload = bool(EDITOR_DEF("text_editor/behavior/files/auto_reload_scripts_on_external_change", false)); - if (shader->get_last_modified_time() != FileAccess::get_modified_time(shader->get_path())) { - if (use_autoreload) { - _reload_shader_from_disk(); - } else { - disk_changed->call_deferred(SNAME("popup_centered")); - } - } -} - -void ShaderEditor::_reload_shader_from_disk() { - Ref<Shader> rel_shader = ResourceLoader::load(shader->get_path(), shader->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); - ERR_FAIL_COND(!rel_shader.is_valid()); - - shader->set_code(rel_shader->get_code()); - shader->set_last_modified_time(rel_shader->get_last_modified_time()); - shader_editor->reload_text(); + return drag_data; } -void ShaderEditor::edit(const Ref<Shader> &p_shader) { - if (p_shader.is_null() || !p_shader->is_text_shader()) { - return; +bool ShaderEditorPlugin::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + Dictionary d = p_data; + if (!d.has("type")) { + return false; } - if (shader == p_shader) { - return; + if (String(d["type"]) == "shader_list_element") { + return true; } - shader = p_shader; + if (String(d["type"]) == "files") { + Vector<String> files = d["files"]; - shader_editor->set_edited_shader(p_shader); - - //vertex_editor->set_edited_shader(shader,ShaderLanguage::SHADER_MATERIAL_VERTEX); - // see if already has it -} - -void ShaderEditor::save_external_data(const String &p_str) { - if (shader.is_null()) { - disk_changed->hide(); - return; - } - - apply_shaders(); - if (!shader->is_built_in()) { - //external shader, save it - ResourceSaver::save(shader->get_path(), shader); - } - - disk_changed->hide(); -} - -void ShaderEditor::apply_shaders() { - if (shader.is_valid()) { - String shader_code = shader->get_code(); - String editor_code = shader_editor->get_text_editor()->get_text(); - if (shader_code != editor_code) { - shader->set_code(editor_code); - shader->set_edited(true); + if (files.size() == 0) { + return false; } - } -} -void ShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { - Ref<InputEventMouseButton> mb = ev; - - if (mb.is_valid()) { - if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { - CodeEdit *tx = shader_editor->get_text_editor(); - - Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position()); - int row = pos.y; - int col = pos.x; - tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click")); - - if (tx->is_move_caret_on_right_click_enabled()) { - if (tx->has_selection()) { - int from_line = tx->get_selection_from_line(); - int to_line = tx->get_selection_to_line(); - int from_column = tx->get_selection_from_column(); - int to_column = tx->get_selection_to_column(); - - if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { - // Right click is outside the selected text - tx->deselect(); - } - } - if (!tx->has_selection()) { - tx->set_caret_line(row, true, false); - tx->set_caret_column(col); + for (int i = 0; i < files.size(); i++) { + String file = files[i]; + if (ResourceLoader::exists(file, "Shader")) { + Ref<Shader> shader = ResourceLoader::load(file); + if (shader.is_valid()) { + return true; } } - _make_context_menu(tx->has_selection(), get_local_mouse_position()); } + return false; } - Ref<InputEventKey> k = ev; - if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) { - CodeEdit *tx = shader_editor->get_text_editor(); - tx->adjust_viewport_to_caret(); - _make_context_menu(tx->has_selection(), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos())); - context_menu->grab_focus(); - } + return false; } -void ShaderEditor::_update_bookmark_list() { - bookmarks_menu->clear(); - - bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); - bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL); - bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT); - bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV); - - Array bookmark_list = shader_editor->get_text_editor()->get_bookmarked_lines(); - if (bookmark_list.size() == 0) { +void ShaderEditorPlugin::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + if (!can_drop_data_fw(p_point, p_data, p_from)) { return; } - bookmarks_menu->add_separator(); - - for (int i = 0; i < bookmark_list.size(); i++) { - String line = shader_editor->get_text_editor()->get_line(bookmark_list[i]).strip_edges(); - // Limit the size of the line if too big. - if (line.length() > 50) { - line = line.substr(0, 50); - } - - bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\""); - bookmarks_menu->set_item_metadata(bookmarks_menu->get_item_count() - 1, bookmark_list[i]); - } -} - -void ShaderEditor::_bookmark_item_pressed(int p_idx) { - if (p_idx < 4) { // Any item before the separator. - _menu_option(bookmarks_menu->get_item_id(p_idx)); - } else { - shader_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx)); - } -} - -void ShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) { - context_menu->clear(); - if (p_selection) { - context_menu->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT); - context_menu->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY); + Dictionary d = p_data; + if (!d.has("type")) { + return; } - context_menu->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE); - context_menu->add_separator(); - context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL); - context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); - context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO); - - context_menu->add_separator(); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_left"), EDIT_INDENT_LEFT); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); - - context_menu->set_position(get_screen_position() + p_position); - context_menu->reset_size(); - context_menu->popup(); -} - -ShaderEditor::ShaderEditor(EditorNode *p_node) { - GLOBAL_DEF("debug/shader_language/warnings/enable", true); - GLOBAL_DEF("debug/shader_language/warnings/treat_warnings_as_errors", false); - for (int i = 0; i < (int)ShaderWarning::WARNING_MAX; i++) { - GLOBAL_DEF("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code((ShaderWarning::Code)i).to_lower(), true); + if (String(d["type"]) == "shader_list_element") { + int idx = d["shader_list_element"]; + int new_idx = shader_list->get_item_at_position(p_point); + _move_shader_tab(idx, new_idx); + return; } - _update_warnings(false); - - shader_editor = memnew(ShaderTextEditor); - shader_editor->set_v_size_flags(SIZE_EXPAND_FILL); - shader_editor->add_theme_constant_override("separation", 0); - shader_editor->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - - shader_editor->connect("show_warnings_panel", callable_mp(this, &ShaderEditor::_show_warnings_panel)); - shader_editor->connect("script_changed", callable_mp(this, &ShaderEditor::apply_shaders)); - EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ShaderEditor::_editor_settings_changed)); - ProjectSettingsEditor::get_singleton()->connect("confirmed", callable_mp(this, &ShaderEditor::_project_settings_changed)); - - shader_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line")); - - shader_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true); - shader_editor->get_text_editor()->set_context_menu_enabled(false); - shader_editor->get_text_editor()->connect("gui_input", callable_mp(this, &ShaderEditor::_text_edit_gui_input)); - - shader_editor->update_editor_settings(); - - context_menu = memnew(PopupMenu); - add_child(context_menu); - context_menu->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); - - VBoxContainer *main_container = memnew(VBoxContainer); - HBoxContainer *hbc = memnew(HBoxContainer); - - edit_menu = memnew(MenuButton); - edit_menu->set_shortcut_context(this); - edit_menu->set_text(TTR("Edit")); - edit_menu->set_switch_on_hover(true); - - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO); - edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE); - edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL); - edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_left"), EDIT_INDENT_LEFT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION); - edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE); - edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); - - search_menu = memnew(MenuButton); - search_menu->set_shortcut_context(this); - search_menu->set_text(TTR("Search")); - search_menu->set_switch_on_hover(true); - - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); - search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); - - MenuButton *goto_menu = memnew(MenuButton); - goto_menu->set_shortcut_context(this); - goto_menu->set_text(TTR("Go To")); - goto_menu->set_switch_on_hover(true); - goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); - - goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE); - goto_menu->get_popup()->add_separator(); - - bookmarks_menu = memnew(PopupMenu); - bookmarks_menu->set_name("Bookmarks"); - goto_menu->get_popup()->add_child(bookmarks_menu); - goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks"); - _update_bookmark_list(); - bookmarks_menu->connect("about_to_popup", callable_mp(this, &ShaderEditor::_update_bookmark_list)); - bookmarks_menu->connect("index_pressed", callable_mp(this, &ShaderEditor::_bookmark_item_pressed)); - - help_menu = memnew(MenuButton); - help_menu->set_text(TTR("Help")); - help_menu->set_switch_on_hover(true); - help_menu->get_popup()->add_icon_item(p_node->get_gui_base()->get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), TTR("Online Docs"), HELP_DOCS); - help_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); - - add_child(main_container); - main_container->add_child(hbc); - hbc->add_child(search_menu); - hbc->add_child(edit_menu); - hbc->add_child(goto_menu); - hbc->add_child(help_menu); - hbc->add_theme_style_override("panel", p_node->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles"))); - - VSplitContainer *editor_box = memnew(VSplitContainer); - main_container->add_child(editor_box); - editor_box->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - editor_box->set_v_size_flags(SIZE_EXPAND_FILL); - editor_box->add_child(shader_editor); - - FindReplaceBar *bar = memnew(FindReplaceBar); - main_container->add_child(bar); - bar->hide(); - shader_editor->set_find_replace_bar(bar); - - warnings_panel = memnew(RichTextLabel); - warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); - warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL); - warnings_panel->set_meta_underline(true); - warnings_panel->set_selection_enabled(true); - warnings_panel->set_focus_mode(FOCUS_CLICK); - warnings_panel->hide(); - warnings_panel->connect("meta_clicked", callable_mp(this, &ShaderEditor::_warning_clicked)); - editor_box->add_child(warnings_panel); - shader_editor->set_warnings_panel(warnings_panel); - - goto_line_dialog = memnew(GotoLineDialog); - add_child(goto_line_dialog); - - disk_changed = memnew(ConfirmationDialog); - - VBoxContainer *vbc = memnew(VBoxContainer); - disk_changed->add_child(vbc); - - Label *dl = memnew(Label); - dl->set_text(TTR("This shader has been modified on disk.\nWhat action should be taken?")); - vbc->add_child(dl); - - disk_changed->connect("confirmed", callable_mp(this, &ShaderEditor::_reload_shader_from_disk)); - disk_changed->get_ok_button()->set_text(TTR("Reload")); - - disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); - disk_changed->connect("custom_action", callable_mp(this, &ShaderEditor::save_external_data)); - - add_child(disk_changed); - - _editor_settings_changed(); -} -void ShaderEditorPlugin::edit(Object *p_object) { - Shader *s = Object::cast_to<Shader>(p_object); - shader_editor->edit(s); -} + if (String(d["type"]) == "files") { + Vector<String> files = d["files"]; -bool ShaderEditorPlugin::handles(Object *p_object) const { - Shader *shader = Object::cast_to<Shader>(p_object); - return shader != nullptr && shader->is_text_shader(); -} - -void ShaderEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - button->show(); - editor->make_bottom_panel_item_visible(shader_editor); + for (int i = 0; i < files.size(); i++) { + String file = files[i]; + if (!ResourceLoader::exists(file, "Shader")) { + continue; + } - } else { - button->hide(); - if (shader_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); + Ref<Resource> res = ResourceLoader::load(file); + if (res.is_valid()) { + edit(res.ptr()); + } } - shader_editor->apply_shaders(); } } -void ShaderEditorPlugin::selected_notify() { - shader_editor->ensure_select_current(); -} - -void ShaderEditorPlugin::save_external_data() { - shader_editor->save_external_data(); -} - -void ShaderEditorPlugin::apply_changes() { - shader_editor->apply_shaders(); -} - -ShaderEditorPlugin::ShaderEditorPlugin(EditorNode *p_node) { - editor = p_node; - shader_editor = memnew(ShaderEditor(p_node)); - - shader_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE); - button = editor->add_bottom_panel_item(TTR("Shader"), shader_editor); - button->hide(); - - _2d = false; +ShaderEditorPlugin::ShaderEditorPlugin() { + main_split = memnew(HSplitContainer); + + VBoxContainer *vb = memnew(VBoxContainer); + + HBoxContainer *file_hb = memnew(HBoxContainer); + vb->add_child(file_hb); + file_menu = memnew(MenuButton); + file_menu->set_text(TTR("File")); + file_menu->get_popup()->add_item(TTR("New Shader"), FILE_NEW); + file_menu->get_popup()->add_item(TTR("New Shader Include"), FILE_NEW_INCLUDE); + file_menu->get_popup()->add_separator(); + file_menu->get_popup()->add_item(TTR("Load Shader File"), FILE_OPEN); + file_menu->get_popup()->add_item(TTR("Load Shader Include File"), FILE_OPEN_INCLUDE); + file_menu->get_popup()->add_item(TTR("Save File"), FILE_SAVE); + file_menu->get_popup()->add_item(TTR("Save File As"), FILE_SAVE_AS); + file_menu->get_popup()->add_separator(); + file_menu->get_popup()->add_item(TTR("Open File in Inspector"), FILE_INSPECT); + file_menu->get_popup()->add_separator(); + file_menu->get_popup()->add_item(TTR("Close File"), FILE_CLOSE); + file_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed)); + file_hb->add_child(file_menu); + + for (int i = FILE_SAVE; i < FILE_MAX; i++) { + file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), true); + } + + shader_list = memnew(ItemList); + shader_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + vb->add_child(shader_list); + shader_list->connect("item_selected", callable_mp(this, &ShaderEditorPlugin::_shader_selected)); + shader_list->connect("item_clicked", callable_mp(this, &ShaderEditorPlugin::_shader_list_clicked)); + SET_DRAG_FORWARDING_GCD(shader_list, ShaderEditorPlugin); + + main_split->add_child(vb); + vb->set_custom_minimum_size(Size2(200, 300) * EDSCALE); + + shader_tabs = memnew(TabContainer); + shader_tabs->set_tabs_visible(false); + shader_tabs->set_h_size_flags(Control::SIZE_EXPAND_FILL); + main_split->add_child(shader_tabs); + Ref<StyleBoxEmpty> empty; + empty.instantiate(); + shader_tabs->add_theme_style_override("panel", empty); + + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Shader Editor"), main_split); + + // Defer connect because Editor class is not in the binding system yet. + EditorNode::get_singleton()->call_deferred("connect", "resource_saved", callable_mp(this, &ShaderEditorPlugin::_resource_saved), CONNECT_DEFERRED); + + shader_create_dialog = memnew(ShaderCreateDialog); + vb->add_child(shader_create_dialog); + shader_create_dialog->connect("shader_created", callable_mp(this, &ShaderEditorPlugin::_shader_created)); + shader_create_dialog->connect("shader_include_created", callable_mp(this, &ShaderEditorPlugin::_shader_include_created)); } ShaderEditorPlugin::~ShaderEditorPlugin() { diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index cc90d381a5..408d08ade0 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -1,183 +1,112 @@ -/*************************************************************************/ -/* shader_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* shader_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 SHADER_EDITOR_PLUGIN_H #define SHADER_EDITOR_PLUGIN_H -#include "editor/code_editor.h" #include "editor/editor_plugin.h" -#include "scene/gui/menu_button.h" -#include "scene/gui/panel_container.h" -#include "scene/gui/rich_text_label.h" -#include "scene/gui/tab_container.h" -#include "scene/gui/text_edit.h" -#include "scene/main/timer.h" -#include "scene/resources/shader.h" -#include "servers/rendering/shader_language.h" - -class ShaderTextEditor : public CodeTextEditor { - GDCLASS(ShaderTextEditor, CodeTextEditor); - - Color marked_line_color = Color(1, 1, 1); - - struct WarningsComparator { - _ALWAYS_INLINE_ bool operator()(const ShaderWarning &p_a, const ShaderWarning &p_b) const { return (p_a.get_line() < p_b.get_line()); } - }; - - Ref<CodeHighlighter> syntax_highlighter; - RichTextLabel *warnings_panel = nullptr; - Ref<Shader> shader; - List<ShaderWarning> warnings; - - void _check_shader_mode(); - void _update_warning_panel(); -protected: - static void _bind_methods(); - virtual void _load_theme_settings() override; - - virtual void _code_complete_script(const String &p_code, List<ScriptCodeCompletionOption> *r_options) override; - -public: - virtual void _validate_script() override; +class HSplitContainer; +class ItemList; +class MenuButton; +class ShaderCreateDialog; +class TabContainer; +class TextShaderEditor; +class VisualShaderEditor; - void reload_text(); - void set_warnings_panel(RichTextLabel *p_warnings_panel); +class ShaderEditorPlugin : public EditorPlugin { + GDCLASS(ShaderEditorPlugin, EditorPlugin); - Ref<Shader> get_edited_shader() const; - void set_edited_shader(const Ref<Shader> &p_shader); - ShaderTextEditor(); -}; + struct EditedShader { + Ref<Shader> shader; + Ref<ShaderInclude> shader_inc; + TextShaderEditor *shader_editor = nullptr; + VisualShaderEditor *visual_shader_editor = nullptr; + }; -class ShaderEditor : public PanelContainer { - GDCLASS(ShaderEditor, PanelContainer); + LocalVector<EditedShader> edited_shaders; + // Always valid operations come first in the enum, file-specific ones + // should go after FILE_SAVE which is used to build the menu accordingly. enum { - EDIT_UNDO, - EDIT_REDO, - EDIT_CUT, - EDIT_COPY, - EDIT_PASTE, - EDIT_SELECT_ALL, - EDIT_MOVE_LINE_UP, - EDIT_MOVE_LINE_DOWN, - EDIT_INDENT_LEFT, - EDIT_INDENT_RIGHT, - EDIT_DELETE_LINE, - EDIT_DUPLICATE_SELECTION, - EDIT_TOGGLE_COMMENT, - EDIT_COMPLETE, - SEARCH_FIND, - SEARCH_FIND_NEXT, - SEARCH_FIND_PREV, - SEARCH_REPLACE, - SEARCH_GOTO_LINE, - BOOKMARK_TOGGLE, - BOOKMARK_GOTO_NEXT, - BOOKMARK_GOTO_PREV, - BOOKMARK_REMOVE_ALL, - HELP_DOCS, + FILE_NEW, + FILE_NEW_INCLUDE, + FILE_OPEN, + FILE_OPEN_INCLUDE, + FILE_SAVE, + FILE_SAVE_AS, + FILE_INSPECT, + FILE_CLOSE, + FILE_MAX }; - MenuButton *edit_menu; - MenuButton *search_menu; - PopupMenu *bookmarks_menu; - MenuButton *help_menu; - PopupMenu *context_menu; - RichTextLabel *warnings_panel = nullptr; - uint64_t idle; - - GotoLineDialog *goto_line_dialog; - ConfirmationDialog *erase_tab_confirm; - ConfirmationDialog *disk_changed; - - ShaderTextEditor *shader_editor; + HSplitContainer *main_split = nullptr; + ItemList *shader_list = nullptr; + TabContainer *shader_tabs = nullptr; - void _menu_option(int p_option); - mutable Ref<Shader> shader; + Button *button = nullptr; + MenuButton *file_menu = nullptr; - void _editor_settings_changed(); - void _project_settings_changed(); + ShaderCreateDialog *shader_create_dialog = nullptr; - void _check_for_external_edit(); - void _reload_shader_from_disk(); - void _show_warnings_panel(bool p_show); - void _warning_clicked(Variant p_line); - void _update_warnings(bool p_validate); + void _update_shader_list(); + void _shader_selected(int p_index); + void _shader_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index); + void _menu_item_pressed(int p_index); + void _resource_saved(Object *obj); + void _close_shader(int p_index); -protected: - void _notification(int p_what); - static void _bind_methods(); - void _make_context_menu(bool p_selection, Vector2 p_position); - void _text_edit_gui_input(const Ref<InputEvent> &ev); - - void _update_bookmark_list(); - void _bookmark_item_pressed(int p_idx); - -public: - void apply_shaders(); - - void ensure_select_current(); - void edit(const Ref<Shader> &p_shader); - - void goto_line_selection(int p_line, int p_begin, int p_end); - - virtual Size2 get_minimum_size() const override { return Size2(0, 200); } - void save_external_data(const String &p_str = ""); - - ShaderEditor(EditorNode *p_node); -}; - -class ShaderEditorPlugin : public EditorPlugin { - GDCLASS(ShaderEditorPlugin, EditorPlugin); + void _shader_created(Ref<Shader> p_shader); + void _shader_include_created(Ref<ShaderInclude> p_shader_inc); + void _update_shader_list_status(); + void _move_shader_tab(int p_from, int p_to); - bool _2d; - ShaderEditor *shader_editor; - EditorNode *editor; - Button *button; + Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); public: virtual String get_name() const override { return "Shader"; } - bool has_main_screen() const override { return false; } virtual void edit(Object *p_object) override; virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; virtual void selected_notify() override; - ShaderEditor *get_shader_editor() const { return shader_editor; } + TextShaderEditor *get_shader_editor(const Ref<Shader> &p_for_shader); + VisualShaderEditor *get_visual_shader_editor(const Ref<Shader> &p_for_shader); virtual void save_external_data() override; virtual void apply_changes() override; - ShaderEditorPlugin(EditorNode *p_node); + ShaderEditorPlugin(); ~ShaderEditorPlugin(); }; -#endif +#endif // SHADER_EDITOR_PLUGIN_H diff --git a/editor/plugins/shader_file_editor_plugin.cpp b/editor/plugins/shader_file_editor_plugin.cpp index 8250164885..a83a48da1f 100644 --- a/editor/plugins/shader_file_editor_plugin.cpp +++ b/editor/plugins/shader_file_editor_plugin.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* shader_file_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* shader_file_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 "shader_file_editor_plugin.h" @@ -37,7 +37,8 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" -#include "editor/property_editor.h" +#include "scene/gui/item_list.h" +#include "scene/gui/split_container.h" #include "servers/display_server.h" #include "servers/rendering/shader_types.h" @@ -200,10 +201,12 @@ void ShaderFileEditor::_update_options() { } void ShaderFileEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_WM_WINDOW_FOCUS_IN) { - if (is_visible_in_tree() && shader_file.is_valid()) { - _update_options(); - } + switch (p_what) { + case NOTIFICATION_WM_WINDOW_FOCUS_IN: { + if (is_visible_in_tree() && shader_file.is_valid()) { + _update_options(); + } + } break; } } @@ -245,7 +248,7 @@ void ShaderFileEditor::_shader_changed() { ShaderFileEditor *ShaderFileEditor::singleton = nullptr; -ShaderFileEditor::ShaderFileEditor(EditorNode *p_node) { +ShaderFileEditor::ShaderFileEditor() { singleton = this; HSplitContainer *main_hs = memnew(HSplitContainer); @@ -280,11 +283,12 @@ ShaderFileEditor::ShaderFileEditor(EditorNode *p_node) { stage_hb->add_child(button); stages[i] = button; button->set_button_group(bg); - button->connect("pressed", callable_mp(this, &ShaderFileEditor::_version_selected), varray(i)); + button->connect("pressed", callable_mp(this, &ShaderFileEditor::_version_selected).bind(i)); } error_text = memnew(RichTextLabel); error_text->set_v_size_flags(SIZE_EXPAND_FILL); + error_text->set_selection_enabled(true); main_vb->add_child(error_text); } @@ -301,22 +305,21 @@ bool ShaderFileEditorPlugin::handles(Object *p_object) const { void ShaderFileEditorPlugin::make_visible(bool p_visible) { if (p_visible) { button->show(); - editor->make_bottom_panel_item_visible(shader_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(shader_editor); } else { button->hide(); if (shader_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); } } } -ShaderFileEditorPlugin::ShaderFileEditorPlugin(EditorNode *p_node) { - editor = p_node; - shader_editor = memnew(ShaderFileEditor(p_node)); +ShaderFileEditorPlugin::ShaderFileEditorPlugin() { + shader_editor = memnew(ShaderFileEditor); shader_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE); - button = editor->add_bottom_panel_item(TTR("ShaderFile"), shader_editor); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("ShaderFile"), shader_editor); button->hide(); } diff --git a/editor/plugins/shader_file_editor_plugin.h b/editor/plugins/shader_file_editor_plugin.h index feec0c206e..989668e37d 100644 --- a/editor/plugins/shader_file_editor_plugin.h +++ b/editor/plugins/shader_file_editor_plugin.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* shader_file_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* shader_file_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 SHADER_FILE_EDITOR_PLUGIN_H #define SHADER_FILE_EDITOR_PLUGIN_H @@ -41,15 +41,17 @@ #include "scene/main/timer.h" #include "servers/rendering/rendering_device_binds.h" +class ItemList; + class ShaderFileEditor : public PanelContainer { GDCLASS(ShaderFileEditor, PanelContainer); Ref<RDShaderFile> shader_file; - HBoxContainer *stage_hb; - ItemList *versions; + HBoxContainer *stage_hb = nullptr; + ItemList *versions = nullptr; Button *stages[RD::SHADER_STAGE_MAX]; - RichTextLabel *error_text; + RichTextLabel *error_text = nullptr; void _update_version(const StringName &p_version_txt, const RenderingDevice::ShaderStage p_stage); void _version_selected(int p_stage); @@ -66,15 +68,14 @@ public: static ShaderFileEditor *singleton; void edit(const Ref<RDShaderFile> &p_shader); - ShaderFileEditor(EditorNode *p_node); + ShaderFileEditor(); }; class ShaderFileEditorPlugin : public EditorPlugin { GDCLASS(ShaderFileEditorPlugin, EditorPlugin); - ShaderFileEditor *shader_editor; - EditorNode *editor; - Button *button; + ShaderFileEditor *shader_editor = nullptr; + Button *button = nullptr; public: virtual String get_name() const override { return "ShaderFile"; } @@ -85,7 +86,7 @@ public: ShaderFileEditor *get_shader_editor() const { return shader_editor; } - ShaderFileEditorPlugin(EditorNode *p_node); + ShaderFileEditorPlugin(); ~ShaderFileEditorPlugin(); }; diff --git a/editor/plugins/skeleton_2d_editor_plugin.cpp b/editor/plugins/skeleton_2d_editor_plugin.cpp index b6d465ea81..06db696330 100644 --- a/editor/plugins/skeleton_2d_editor_plugin.cpp +++ b/editor/plugins/skeleton_2d_editor_plugin.cpp @@ -1,38 +1,41 @@ -/*************************************************************************/ -/* skeleton_2d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* skeleton_2d_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 "skeleton_2d_editor_plugin.h" #include "canvas_item_editor_plugin.h" +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" #include "scene/2d/mesh_instance_2d.h" #include "scene/gui/box_container.h" +#include "scene/gui/menu_button.h" #include "thirdparty/misc/clipper.hpp" void Skeleton2DEditor::_node_removed(Node *p_node) { @@ -58,7 +61,7 @@ void Skeleton2DEditor::_menu_option(int p_option) { err_dialog->popup_centered(); return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Rest Pose to Bones")); for (int i = 0; i < node->get_bone_count(); i++) { Bone2D *bone = node->get_bone(i); @@ -74,7 +77,7 @@ void Skeleton2DEditor::_menu_option(int p_option) { err_dialog->popup_centered(); return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Create Rest Pose from Bones")); for (int i = 0; i < node->get_bone_count(); i++) { Bone2D *bone = node->get_bone(i); @@ -127,10 +130,9 @@ void Skeleton2DEditorPlugin::make_visible(bool p_visible) { } } -Skeleton2DEditorPlugin::Skeleton2DEditorPlugin(EditorNode *p_node) { - editor = p_node; +Skeleton2DEditorPlugin::Skeleton2DEditorPlugin() { sprite_editor = memnew(Skeleton2DEditor); - editor->get_main_control()->add_child(sprite_editor); + EditorNode::get_singleton()->get_main_screen_control()->add_child(sprite_editor); make_visible(false); //sprite_editor->options->hide(); diff --git a/editor/plugins/skeleton_2d_editor_plugin.h b/editor/plugins/skeleton_2d_editor_plugin.h index 2fa7f02622..e4551593f3 100644 --- a/editor/plugins/skeleton_2d_editor_plugin.h +++ b/editor/plugins/skeleton_2d_editor_plugin.h @@ -1,41 +1,43 @@ -/*************************************************************************/ -/* skeleton_2d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* skeleton_2d_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 SKELETON_2D_EDITOR_PLUGIN_H #define SKELETON_2D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/2d/skeleton_2d.h" #include "scene/gui/spin_box.h" +class AcceptDialog; +class MenuButton; + class Skeleton2DEditor : public Control { GDCLASS(Skeleton2DEditor, Control); @@ -44,10 +46,10 @@ class Skeleton2DEditor : public Control { MENU_OPTION_MAKE_REST, }; - Skeleton2D *node; + Skeleton2D *node = nullptr; - MenuButton *options; - AcceptDialog *err_dialog; + MenuButton *options = nullptr; + AcceptDialog *err_dialog = nullptr; void _menu_option(int p_option); @@ -66,8 +68,7 @@ public: class Skeleton2DEditorPlugin : public EditorPlugin { GDCLASS(Skeleton2DEditorPlugin, EditorPlugin); - Skeleton2DEditor *sprite_editor; - EditorNode *editor; + Skeleton2DEditor *sprite_editor = nullptr; public: virtual String get_name() const override { return "Skeleton2D"; } @@ -76,7 +77,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - Skeleton2DEditorPlugin(EditorNode *p_node); + Skeleton2DEditorPlugin(); ~Skeleton2DEditorPlugin(); }; diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index e1b27cb045..120cfbdefb 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -1,48 +1,53 @@ -/*************************************************************************/ -/* skeleton_3d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* skeleton_3d_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 "skeleton_3d_editor_plugin.h" #include "core/io/resource_saver.h" -#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_properties.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "node_3d_editor_plugin.h" #include "scene/3d/collision_shape_3d.h" #include "scene/3d/joint_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/physics_body_3d.h" +#include "scene/gui/separator.h" #include "scene/resources/capsule_shape_3d.h" +#include "scene/resources/skeleton_profile.h" #include "scene/resources/sphere_shape_3d.h" #include "scene/resources/surface_tool.h" +#include "scene/scene_string_names.h" void BoneTransformEditor::create_editors() { const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); @@ -102,8 +107,7 @@ void BoneTransformEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { create_editors(); - break; - } + } break; } } @@ -112,6 +116,7 @@ void BoneTransformEditor::_value_changed(const String &p_property, Variant p_val return; } if (skeleton) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); undo_redo->add_undo_property(skeleton, p_property, skeleton->get(p_property)); undo_redo->add_do_property(skeleton, p_property, p_value); @@ -121,7 +126,6 @@ void BoneTransformEditor::_value_changed(const String &p_property, Variant p_val BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) : skeleton(p_skeleton) { - undo_redo = EditorNode::get_undo_redo(); } void BoneTransformEditor::set_keyable(const bool p_keyable) { @@ -149,11 +153,15 @@ void BoneTransformEditor::set_target(const String &p_prop) { void BoneTransformEditor::_property_keyed(const String &p_path, bool p_advance) { AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); + if (!te || !te->has_keying()) { + return; + } + te->_clear_selection(); PackedStringArray split = p_path.split("/"); if (split.size() == 3 && split[0] == "bones") { int bone_idx = split[1].to_int(); if (split[2] == "position") { - te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_POSITION_3D, skeleton->get(p_path)); + te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_POSITION_3D, (Vector3)skeleton->get(p_path) / skeleton->get_motion_scale()); } if (split[2] == "rotation") { te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_ROTATION_3D, skeleton->get(p_path)); @@ -178,27 +186,27 @@ void BoneTransformEditor::_update_properties() { if (split[2] == "enabled") { enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); enabled_checkbox->update_property(); - enabled_checkbox->update(); + enabled_checkbox->queue_redraw(); } if (split[2] == "position") { position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); position_property->update_property(); - position_property->update(); + position_property->queue_redraw(); } if (split[2] == "rotation") { rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); rotation_property->update_property(); - rotation_property->update(); + rotation_property->queue_redraw(); } if (split[2] == "scale") { scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); scale_property->update_property(); - scale_property->update(); + scale_property->queue_redraw(); } if (split[2] == "rest") { rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); rest_matrix->update_property(); - rest_matrix->update(); + rest_matrix->queue_redraw(); } } } @@ -217,7 +225,7 @@ void Skeleton3DEditor::set_keyable(const bool p_keyable) { }; void Skeleton3DEditor::set_bone_options_enabled(const bool p_bone_options_enabled) { - skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INIT_SELECTED_POSES, !p_bone_options_enabled); + skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_RESET_SELECTED_POSES, !p_bone_options_enabled); skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_SELECTED_POSES_TO_RESTS, !p_bone_options_enabled); }; @@ -227,12 +235,12 @@ void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) { } switch (p_skeleton_option) { - case SKELETON_OPTION_INIT_ALL_POSES: { - init_pose(true); + case SKELETON_OPTION_RESET_ALL_POSES: { + reset_pose(true); break; } - case SKELETON_OPTION_INIT_SELECTED_POSES: { - init_pose(false); + case SKELETON_OPTION_RESET_SELECTED_POSES: { + reset_pose(false); break; } case SKELETON_OPTION_ALL_POSES_TO_RESTS: { @@ -247,43 +255,41 @@ void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) { create_physical_skeleton(); break; } + case SKELETON_OPTION_EXPORT_SKELETON_PROFILE: { + export_skeleton_profile(); + break; + } } } -void Skeleton3DEditor::init_pose(const bool p_all_bones) { +void Skeleton3DEditor::reset_pose(const bool p_all_bones) { if (!skeleton) { return; } - const int bone_len = skeleton->get_bone_count(); - if (!bone_len) { + const int bone_count = skeleton->get_bone_count(); + if (!bone_count) { return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); if (p_all_bones) { - for (int i = 0; i < bone_len; i++) { - Transform3D rest = skeleton->get_bone_rest(i); - ur->add_do_method(skeleton, "set_bone_pose_position", i, rest.origin); - ur->add_do_method(skeleton, "set_bone_pose_rotation", i, rest.basis.get_rotation_quaternion()); - ur->add_do_method(skeleton, "set_bone_pose_scale", i, rest.basis.get_scale()); + for (int i = 0; i < bone_count; i++) { ur->add_undo_method(skeleton, "set_bone_pose_position", i, skeleton->get_bone_pose_position(i)); ur->add_undo_method(skeleton, "set_bone_pose_rotation", i, skeleton->get_bone_pose_rotation(i)); ur->add_undo_method(skeleton, "set_bone_pose_scale", i, skeleton->get_bone_pose_scale(i)); } + ur->add_do_method(skeleton, "reset_bone_poses"); } else { // Todo: Do method with multiple bone selection. if (selected_bone == -1) { ur->commit_action(); return; } - Transform3D rest = skeleton->get_bone_rest(selected_bone); - ur->add_do_method(skeleton, "set_bone_pose_position", selected_bone, rest.origin); - ur->add_do_method(skeleton, "set_bone_pose_rotation", selected_bone, rest.basis.get_rotation_quaternion()); - ur->add_do_method(skeleton, "set_bone_pose_scale", selected_bone, rest.basis.get_scale()); ur->add_undo_method(skeleton, "set_bone_pose_position", selected_bone, skeleton->get_bone_pose_position(selected_bone)); ur->add_undo_method(skeleton, "set_bone_pose_rotation", selected_bone, skeleton->get_bone_pose_rotation(selected_bone)); ur->add_undo_method(skeleton, "set_bone_pose_scale", selected_bone, skeleton->get_bone_pose_scale(selected_bone)); + ur->add_do_method(skeleton, "reset_bone_pose", selected_bone); } ur->commit_action(); } @@ -311,7 +317,7 @@ void Skeleton3DEditor::insert_keys(const bool p_all_bones) { } if (pos_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_POSITION_3D))) { - te->insert_transform_key(skeleton, name, Animation::TYPE_POSITION_3D, skeleton->get_bone_pose_position(i)); + te->insert_transform_key(skeleton, name, Animation::TYPE_POSITION_3D, skeleton->get_bone_pose_position(i) / skeleton->get_motion_scale()); } if (rot_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_ROTATION_3D))) { te->insert_transform_key(skeleton, name, Animation::TYPE_ROTATION_3D, skeleton->get_bone_pose_rotation(i)); @@ -327,15 +333,15 @@ void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) { if (!skeleton) { return; } - const int bone_len = skeleton->get_bone_count(); - if (!bone_len) { + const int bone_count = skeleton->get_bone_count(); + if (!bone_count) { return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Bone Rest"), UndoRedo::MERGE_ENDS); if (p_all_bones) { - for (int i = 0; i < bone_len; i++) { + for (int i = 0; i < bone_count; i++) { ur->add_do_method(skeleton, "set_bone_rest", i, skeleton->get_bone_pose(i)); ur->add_undo_method(skeleton, "set_bone_rest", i, skeleton->get_bone_rest(i)); } @@ -352,25 +358,26 @@ void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) { } void Skeleton3DEditor::create_physical_skeleton() { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ERR_FAIL_COND(!get_tree()); - Node *owner = skeleton == get_tree()->get_edited_scene_root() ? skeleton : skeleton->get_owner(); + Node *owner = get_tree()->get_edited_scene_root(); - const int bc = skeleton->get_bone_count(); + const int bone_count = skeleton->get_bone_count(); - if (!bc) { + if (!bone_count) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Cannot create a physical skeleton for a Skeleton3D node with no bones."))); return; } Vector<BoneInfo> bones_infos; - bones_infos.resize(bc); + bones_infos.resize(bone_count); - for (int bone_id = 0; bc > bone_id; ++bone_id) { + ur->create_action(TTR("Create physical bones"), UndoRedo::MERGE_ALL); + for (int bone_id = 0; bone_count > bone_id; ++bone_id) { const int parent = skeleton->get_bone_parent(bone_id); if (parent < 0) { bones_infos.write[bone_id].relative_rest = skeleton->get_bone_rest(bone_id); - } else { const int parent_parent = skeleton->get_bone_parent(parent); @@ -378,25 +385,33 @@ void Skeleton3DEditor::create_physical_skeleton() { // Create physical bone on parent. if (!bones_infos[parent].physical_bone) { - bones_infos.write[parent].physical_bone = create_physical_bone(parent, bone_id, bones_infos); - - ur->create_action(TTR("Create physical bones")); - ur->add_do_method(skeleton, "add_child", bones_infos[parent].physical_bone); - ur->add_do_reference(bones_infos[parent].physical_bone); - ur->add_undo_method(skeleton, "remove_child", bones_infos[parent].physical_bone); - ur->commit_action(); + PhysicalBone3D *physical_bone = create_physical_bone(parent, bone_id, bones_infos); + if (physical_bone && physical_bone->get_child(0)) { + CollisionShape3D *collision_shape = Object::cast_to<CollisionShape3D>(physical_bone->get_child(0)); + if (collision_shape) { + bones_infos.write[parent].physical_bone = physical_bone; + + ur->add_do_method(skeleton, "add_child", physical_bone); + ur->add_do_method(physical_bone, "set_owner", owner); + ur->add_do_method(collision_shape, "set_owner", owner); + ur->add_do_property(physical_bone, "bone_name", skeleton->get_bone_name(parent)); + + // Create joint between parent of parent. + if (parent_parent != -1) { + ur->add_do_method(physical_bone, "set_joint_type", PhysicalBone3D::JOINT_TYPE_PIN); + } - bones_infos[parent].physical_bone->set_bone_name(skeleton->get_bone_name(parent)); - bones_infos[parent].physical_bone->set_owner(owner); - bones_infos[parent].physical_bone->get_child(0)->set_owner(owner); // set shape owner + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, physical_bone); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, collision_shape); - // Create joint between parent of parent. - if (-1 != parent_parent) { - bones_infos[parent].physical_bone->set_joint_type(PhysicalBone3D::JOINT_TYPE_PIN); + ur->add_do_reference(physical_bone); + ur->add_undo_method(skeleton, "remove_child", physical_bone); + } } } } } + ur->commit_action(); } PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos) { @@ -411,13 +426,22 @@ PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_chi CollisionShape3D *bone_shape = memnew(CollisionShape3D); bone_shape->set_shape(bone_shape_capsule); + bone_shape->set_name("CollisionShape3D"); Transform3D capsule_transform; - capsule_transform.basis = Basis(Vector3(1, 0, 0), Vector3(0, 0, 1), Vector3(0, -1, 0)); + capsule_transform.basis.rows[0] = Vector3(1, 0, 0); + capsule_transform.basis.rows[1] = Vector3(0, 0, 1); + capsule_transform.basis.rows[2] = Vector3(0, -1, 0); bone_shape->set_transform(capsule_transform); + /// Get an up vector not collinear with child rest origin + Vector3 up = Vector3(0, 1, 0); + if (up.cross(child_rest.origin).is_zero_approx()) { + up = Vector3(0, 0, 1); + } + Transform3D body_transform; - body_transform.basis = Basis::looking_at(child_rest.origin); + body_transform.basis = Basis::looking_at(child_rest.origin, up); body_transform.origin = body_transform.basis.xform(Vector3(0, 0, -half_height)); Transform3D joint_transform; @@ -431,6 +455,78 @@ PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_chi return physical_bone; } +void Skeleton3DEditor::export_skeleton_profile() { + if (!skeleton->get_bone_count()) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Cannot export a SkeletonProfile for a Skeleton3D node with no bones."))); + return; + } + + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + file_dialog->set_title(TTR("Export Skeleton Profile As...")); + + List<String> exts; + ResourceLoader::get_recognized_extensions_for_type("SkeletonProfile", &exts); + file_dialog->clear_filters(); + for (const String &K : exts) { + file_dialog->add_filter("*." + K); + } + + file_dialog->popup_file_dialog(); +} + +void Skeleton3DEditor::_file_selected(const String &p_file) { + // Export SkeletonProfile. + Ref<SkeletonProfile> sp(memnew(SkeletonProfile)); + + // Build SkeletonProfile. + sp->set_group_size(1); + + Vector<Vector2> handle_positions; + Vector2 position_max; + Vector2 position_min; + + const int bone_count = skeleton->get_bone_count(); + sp->set_bone_size(bone_count); + for (int i = 0; i < bone_count; i++) { + sp->set_bone_name(i, skeleton->get_bone_name(i)); + int parent = skeleton->get_bone_parent(i); + if (parent >= 0) { + sp->set_bone_parent(i, skeleton->get_bone_name(parent)); + } + sp->set_reference_pose(i, skeleton->get_bone_rest(i)); + + Transform3D grest = skeleton->get_bone_global_rest(i); + handle_positions.append(Vector2(grest.origin.x, grest.origin.y)); + if (i == 0) { + position_max = Vector2(grest.origin.x, grest.origin.y); + position_min = Vector2(grest.origin.x, grest.origin.y); + } else { + position_max.x = MAX(grest.origin.x, position_max.x); + position_max.y = MAX(grest.origin.y, position_max.y); + position_min.x = MIN(grest.origin.x, position_min.x); + position_min.y = MIN(grest.origin.y, position_min.y); + } + } + + // Layout handles provisionaly. + Vector2 bound = Vector2(position_max.x - position_min.x, position_max.y - position_min.y); + Vector2 center = Vector2((position_max.x + position_min.x) * 0.5, (position_max.y + position_min.y) * 0.5); + float nrm = MAX(bound.x, bound.y); + if (nrm > 0) { + for (int i = 0; i < bone_count; i++) { + handle_positions.write[i] = (handle_positions[i] - center) / nrm * 0.9; + sp->set_handle_offset(i, Vector2(0.5 + handle_positions[i].x, 0.5 - handle_positions[i].y)); + } + } + + Error err = ResourceSaver::save(sp, p_file); + + if (err != OK) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file: %s"), p_file)); + return; + } +} + Variant Skeleton3DEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { TreeItem *selected = joint_tree->get_selected(); @@ -499,25 +595,25 @@ void Skeleton3DEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx) { Node *node = get_node_or_null(p_skeleton_path); - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node); - ERR_FAIL_NULL(skeleton); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Skeleton3D *skeleton_node = Object::cast_to<Skeleton3D>(node); + ERR_FAIL_NULL(skeleton_node); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Bone Parentage")); // If the target is a child of ourselves, we move only *us* and not our children. - if (skeleton->is_bone_parent_of(p_target_boneidx, p_selected_boneidx)) { - const BoneId parent_idx = skeleton->get_bone_parent(p_selected_boneidx); - const int bone_count = skeleton->get_bone_count(); + if (skeleton_node->is_bone_parent_of(p_target_boneidx, p_selected_boneidx)) { + const BoneId parent_idx = skeleton_node->get_bone_parent(p_selected_boneidx); + const int bone_count = skeleton_node->get_bone_count(); for (BoneId i = 0; i < bone_count; ++i) { - if (skeleton->get_bone_parent(i) == p_selected_boneidx) { - ur->add_undo_method(skeleton, "set_bone_parent", i, skeleton->get_bone_parent(i)); - ur->add_do_method(skeleton, "set_bone_parent", i, parent_idx); - skeleton->set_bone_parent(i, parent_idx); + if (skeleton_node->get_bone_parent(i) == p_selected_boneidx) { + ur->add_undo_method(skeleton_node, "set_bone_parent", i, skeleton_node->get_bone_parent(i)); + ur->add_do_method(skeleton_node, "set_bone_parent", i, parent_idx); + skeleton_node->set_bone_parent(i, parent_idx); } } } - ur->add_undo_method(skeleton, "set_bone_parent", p_selected_boneidx, skeleton->get_bone_parent(p_selected_boneidx)); - ur->add_do_method(skeleton, "set_bone_parent", p_selected_boneidx, p_target_boneidx); - skeleton->set_bone_parent(p_selected_boneidx, p_target_boneidx); + ur->add_undo_method(skeleton_node, "set_bone_parent", p_selected_boneidx, skeleton_node->get_bone_parent(p_selected_boneidx)); + ur->add_do_method(skeleton_node, "set_bone_parent", p_selected_boneidx, p_target_boneidx); + skeleton_node->set_bone_parent(p_selected_boneidx, p_target_boneidx); update_joint_tree(); ur->commit_action(); @@ -527,24 +623,29 @@ void Skeleton3DEditor::_joint_tree_selection_changed() { TreeItem *selected = joint_tree->get_selected(); if (selected) { const String path = selected->get_metadata(0); - - if (path.begins_with("bones/")) { - const int b_idx = path.get_slicec('/', 1).to_int(); + if (!path.begins_with("bones/")) { + return; + } + const int b_idx = path.get_slicec('/', 1).to_int(); + selected_bone = b_idx; + if (pose_editor) { const String bone_path = "bones/" + itos(b_idx) + "/"; - pose_editor->set_target(bone_path); pose_editor->set_keyable(keyable); - selected_bone = b_idx; } } - pose_editor->set_visible(selected); + + if (pose_editor && pose_editor->is_inside_tree()) { + pose_editor->set_visible(selected); + } set_bone_options_enabled(selected); + _update_properties(); _update_gizmo_visible(); } // May be not used with single select mode. -void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos) { +void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) { } void Skeleton3DEditor::_update_properties() { @@ -563,7 +664,7 @@ void Skeleton3DEditor::update_joint_tree() { TreeItem *root = joint_tree->create_item(); - Map<int, TreeItem *> items; + HashMap<int, TreeItem *> items; items.insert(-1, root); @@ -575,7 +676,7 @@ void Skeleton3DEditor::update_joint_tree() { bones_to_process.erase(current_bone_idx); const int parent_idx = skeleton->get_bone_parent(current_bone_idx); - TreeItem *parent_item = items.find(parent_idx)->get(); + TreeItem *parent_item = items.find(parent_idx)->value; TreeItem *joint_item = joint_tree->create_item(parent_item); items.insert(current_bone_idx, joint_item); @@ -599,13 +700,16 @@ void Skeleton3DEditor::update_editors() { void Skeleton3DEditor::create_editors() { set_h_size_flags(SIZE_EXPAND_FILL); - add_theme_constant_override("separation", 0); - set_focus_mode(FOCUS_ALL); Node3DEditor *ne = Node3DEditor::get_singleton(); AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); + // Create File dialog. + file_dialog = memnew(EditorFileDialog); + file_dialog->connect("file_selected", callable_mp(this, &Skeleton3DEditor::_file_selected)); + add_child(file_dialog); + // Create Top Menu Bar. separator = memnew(VSeparator); ne->add_control_to_menu_panel(separator); @@ -615,15 +719,15 @@ void Skeleton3DEditor::create_editors() { ne->add_control_to_menu_panel(skeleton_options); skeleton_options->set_text(TTR("Skeleton3D")); - skeleton_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); // Skeleton options. PopupMenu *p = skeleton_options->get_popup(); - p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/init_all_poses", TTR("Init all Poses")), SKELETON_OPTION_INIT_ALL_POSES); - p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/init_selected_poses", TTR("Init selected Poses")), SKELETON_OPTION_INIT_SELECTED_POSES); - p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/all_poses_to_rests", TTR("Apply all poses to rests")), SKELETON_OPTION_ALL_POSES_TO_RESTS); - p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/selected_poses_to_rests", TTR("Apply selected poses to rests")), SKELETON_OPTION_SELECTED_POSES_TO_RESTS); - p->add_item(TTR("Create physical skeleton"), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON); + p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/reset_all_poses", TTR("Reset All Bone Poses")), SKELETON_OPTION_RESET_ALL_POSES); + p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/reset_selected_poses", TTR("Reset Selected Poses")), SKELETON_OPTION_RESET_SELECTED_POSES); + p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/all_poses_to_rests", TTR("Apply All Poses to Rests")), SKELETON_OPTION_ALL_POSES_TO_RESTS); + p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/selected_poses_to_rests", TTR("Apply Selected Poses to Rests")), SKELETON_OPTION_SELECTED_POSES_TO_RESTS); + p->add_item(TTR("Create Physical Skeleton"), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON); + p->add_item(TTR("Export Skeleton Profile"), SKELETON_OPTION_EXPORT_SKELETON_PROFILE); p->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option)); set_bone_options_enabled(false); @@ -636,7 +740,7 @@ void Skeleton3DEditor::create_editors() { edit_mode_button->set_flat(true); edit_mode_button->set_toggle_mode(true); edit_mode_button->set_focus_mode(FOCUS_NONE); - edit_mode_button->set_tooltip(TTR("Edit Mode\nShow buttons on joints.")); + edit_mode_button->set_tooltip_text(TTR("Edit Mode\nShow buttons on joints.")); edit_mode_button->connect("toggled", callable_mp(this, &Skeleton3DEditor::edit_mode_toggled)); edit_mode = false; @@ -657,7 +761,7 @@ void Skeleton3DEditor::create_editors() { key_loc_button->set_toggle_mode(true); key_loc_button->set_pressed(false); key_loc_button->set_focus_mode(FOCUS_NONE); - key_loc_button->set_tooltip(TTR("Translation mask for inserting keys.")); + key_loc_button->set_tooltip_text(TTR("Translation mask for inserting keys.")); animation_hb->add_child(key_loc_button); key_rot_button = memnew(Button); @@ -665,7 +769,7 @@ void Skeleton3DEditor::create_editors() { key_rot_button->set_toggle_mode(true); key_rot_button->set_pressed(true); key_rot_button->set_focus_mode(FOCUS_NONE); - key_rot_button->set_tooltip(TTR("Rotation mask for inserting keys.")); + key_rot_button->set_tooltip_text(TTR("Rotation mask for inserting keys.")); animation_hb->add_child(key_rot_button); key_scale_button = memnew(Button); @@ -673,23 +777,23 @@ void Skeleton3DEditor::create_editors() { key_scale_button->set_toggle_mode(true); key_scale_button->set_pressed(false); key_scale_button->set_focus_mode(FOCUS_NONE); - key_scale_button->set_tooltip(TTR("Scale mask for inserting keys.")); + key_scale_button->set_tooltip_text(TTR("Scale mask for inserting keys.")); animation_hb->add_child(key_scale_button); key_insert_button = memnew(Button); key_insert_button->set_flat(true); key_insert_button->set_focus_mode(FOCUS_NONE); - key_insert_button->connect("pressed", callable_mp(this, &Skeleton3DEditor::insert_keys), varray(false)); - key_insert_button->set_tooltip(TTR("Insert key of bone poses already exist track.")); + key_insert_button->connect("pressed", callable_mp(this, &Skeleton3DEditor::insert_keys).bind(false)); + key_insert_button->set_tooltip_text(TTR("Insert key of bone poses already exist track.")); key_insert_button->set_shortcut(ED_SHORTCUT("skeleton_3d_editor/insert_key_to_existing_tracks", TTR("Insert Key (Existing Tracks)"), Key::INSERT)); animation_hb->add_child(key_insert_button); key_insert_all_button = memnew(Button); key_insert_all_button->set_flat(true); key_insert_all_button->set_focus_mode(FOCUS_NONE); - key_insert_all_button->connect("pressed", callable_mp(this, &Skeleton3DEditor::insert_keys), varray(true)); - key_insert_all_button->set_tooltip(TTR("Insert key of all bone poses.")); - key_insert_all_button->set_shortcut(ED_SHORTCUT("skeleton_3d_editor/insert_key_of_all_bones", TTR("Insert Key (All Bones)"), KeyModifierMask::CMD + Key::INSERT)); + key_insert_all_button->connect("pressed", callable_mp(this, &Skeleton3DEditor::insert_keys).bind(true)); + key_insert_all_button->set_tooltip_text(TTR("Insert key of all bone poses.")); + key_insert_all_button->set_shortcut(ED_SHORTCUT("skeleton_3d_editor/insert_key_of_all_bones", TTR("Insert Key (All Bones)"), KeyModifierMask::CMD_OR_CTRL + Key::INSERT)); animation_hb->add_child(key_insert_all_button); // Bone tree. @@ -713,7 +817,7 @@ void Skeleton3DEditor::create_editors() { joint_tree->set_v_size_flags(SIZE_EXPAND_FILL); joint_tree->set_h_size_flags(SIZE_EXPAND_FILL); joint_tree->set_allow_rmb_select(true); - joint_tree->set_drag_forwarding(this); + SET_DRAG_FORWARDING_GCD(joint_tree, Skeleton3DEditor); s_con->add_child(joint_tree); pose_editor = memnew(BoneTransformEditor(skeleton)); @@ -726,30 +830,50 @@ void Skeleton3DEditor::create_editors() { void Skeleton3DEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: { - edit_mode_button->set_icon(get_theme_icon(SNAME("ToolBoneSelect"), SNAME("EditorIcons"))); - key_loc_button->set_icon(get_theme_icon(SNAME("KeyPosition"), SNAME("EditorIcons"))); - key_rot_button->set_icon(get_theme_icon(SNAME("KeyRotation"), SNAME("EditorIcons"))); - key_scale_button->set_icon(get_theme_icon(SNAME("KeyScale"), SNAME("EditorIcons"))); - key_insert_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); - key_insert_all_button->set_icon(get_theme_icon(SNAME("NewKey"), SNAME("EditorIcons"))); - get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT); - break; - } case NOTIFICATION_ENTER_TREE: { - create_editors(); update_joint_tree(); update_editors(); + joint_tree->connect("item_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed)); - joint_tree->connect("item_rmb_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select)); + joint_tree->connect("item_mouse_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select)); #ifdef TOOLS_ENABLED skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); skeleton->connect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); skeleton->connect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); #endif - break; - } + + get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Object::CONNECT_ONE_SHOT); + } break; + case NOTIFICATION_READY: { + // Will trigger NOTIFICATION_THEME_CHANGED, but won't cause any loops if called here. + add_theme_constant_override("separation", 0); + } break; + case NOTIFICATION_THEME_CHANGED: { + skeleton_options->set_icon(get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); + edit_mode_button->set_icon(get_theme_icon(SNAME("ToolBoneSelect"), SNAME("EditorIcons"))); + key_loc_button->set_icon(get_theme_icon(SNAME("KeyPosition"), SNAME("EditorIcons"))); + key_rot_button->set_icon(get_theme_icon(SNAME("KeyRotation"), SNAME("EditorIcons"))); + key_scale_button->set_icon(get_theme_icon(SNAME("KeyScale"), SNAME("EditorIcons"))); + key_insert_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); + key_insert_all_button->set_icon(get_theme_icon(SNAME("NewKey"), SNAME("EditorIcons"))); + + update_joint_tree(); + } break; + case NOTIFICATION_PREDELETE: { + if (skeleton) { + select_bone(-1); // Requires that the joint_tree has not been deleted. +#ifdef TOOLS_ENABLED + skeleton->disconnect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); + skeleton->disconnect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); + skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); + skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); + skeleton->set_transform_gizmo_visible(true); +#endif + handles_mesh_instance->get_parent()->remove_child(handles_mesh_instance); + } + edit_mode_toggled(false); + } break; } } @@ -769,10 +893,6 @@ void Skeleton3DEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_properties"), &Skeleton3DEditor::_update_properties); ClassDB::bind_method(D_METHOD("_on_click_skeleton_option"), &Skeleton3DEditor::_on_click_skeleton_option); - ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("drop_data_fw"), &Skeleton3DEditor::drop_data_fw); - ClassDB::bind_method(D_METHOD("move_skeleton_bone"), &Skeleton3DEditor::move_skeleton_bone); ClassDB::bind_method(D_METHOD("_draw_gizmo"), &Skeleton3DEditor::_draw_gizmo); @@ -783,8 +903,7 @@ void Skeleton3DEditor::edit_mode_toggled(const bool pressed) { _update_gizmo_visible(); } -Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *p_skeleton) : - editor(p_editor), +Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, Skeleton3D *p_skeleton) : editor_plugin(e_plugin), skeleton(p_skeleton) { singleton = this; @@ -797,14 +916,14 @@ Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, Edit shader_type spatial; render_mode unshaded, shadows_disabled, depth_draw_always; -uniform sampler2D texture_albedo : hint_albedo; +uniform sampler2D texture_albedo : source_color; uniform float point_size : hint_range(0,128) = 32; void vertex() { if (!OUTPUT_IS_SRGB) { COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); } VERTEX = VERTEX; - POSITION=PROJECTION_MATRIX*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0); + POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); POSITION.z = mix(POSITION.z, 0, 0.999); POINT_SIZE = point_size; } @@ -818,14 +937,16 @@ void fragment() { } )"); handle_material->set_shader(handle_shader); - Ref<Texture2D> handle = editor->get_gui_base()->get_theme_icon("EditorBoneHandle", "EditorIcons"); - handle_material->set_shader_param("point_size", handle->get_width()); - handle_material->set_shader_param("texture_albedo", handle); + Ref<Texture2D> handle = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("EditorBoneHandle"), SNAME("EditorIcons")); + handle_material->set_shader_parameter("point_size", handle->get_width()); + handle_material->set_shader_parameter("texture_albedo", handle); handles_mesh_instance = memnew(MeshInstance3D); handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); handles_mesh.instantiate(); handles_mesh_instance->set_mesh(handles_mesh); + + create_editors(); } void Skeleton3DEditor::update_bone_original() { @@ -863,25 +984,31 @@ void Skeleton3DEditor::_draw_gizmo() { } void Skeleton3DEditor::_draw_handles() { - handles_mesh_instance->show(); + const int bone_count = skeleton->get_bone_count(); - const int bone_len = skeleton->get_bone_count(); handles_mesh->clear_surfaces(); - handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS); - for (int i = 0; i < bone_len; i++) { - Color c; - if (i == selected_bone) { - c = Color(1, 1, 0); - } else { - c = Color(0.1, 0.25, 0.8); + if (bone_count) { + handles_mesh_instance->show(); + + handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS); + + for (int i = 0; i < bone_count; i++) { + Color c; + if (i == selected_bone) { + c = Color(1, 1, 0); + } else { + c = Color(0.1, 0.25, 0.8); + } + Vector3 point = skeleton->get_bone_global_pose(i).origin; + handles_mesh->surface_set_color(c); + handles_mesh->surface_add_vertex(point); } - Vector3 point = skeleton->get_bone_global_pose(i).origin; - handles_mesh->surface_set_color(c); - handles_mesh->surface_add_vertex(point); + handles_mesh->surface_end(); + handles_mesh->surface_set_material(0, handle_material); + } else { + handles_mesh_instance->hide(); } - handles_mesh->surface_end(); - handles_mesh->surface_set_material(0, handle_material); } TreeItem *Skeleton3DEditor::_find(TreeItem *p_node, const NodePath &p_path) { @@ -963,20 +1090,9 @@ void Skeleton3DEditor::select_bone(int p_idx) { } Skeleton3DEditor::~Skeleton3DEditor() { - if (skeleton) { - select_bone(-1); -#ifdef TOOLS_ENABLED - skeleton->disconnect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); - skeleton->disconnect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); - skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); - skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); - skeleton->set_transform_gizmo_visible(true); -#endif - handles_mesh_instance->get_parent()->remove_child(handles_mesh_instance); - } - edit_mode_toggled(false); + singleton = nullptr; - handles_mesh_instance->queue_delete(); + handles_mesh_instance->queue_free(); Node3DEditor *ne = Node3DEditor::get_singleton(); @@ -1009,15 +1125,12 @@ void EditorInspectorPluginSkeleton::parse_begin(Object *p_object) { Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_object); ERR_FAIL_COND(!skeleton); - skel_editor = memnew(Skeleton3DEditor(this, editor, skeleton)); + skel_editor = memnew(Skeleton3DEditor(this, skeleton)); add_custom_control(skel_editor); } -Skeleton3DEditorPlugin::Skeleton3DEditorPlugin(EditorNode *p_node) { - editor = p_node; - +Skeleton3DEditorPlugin::Skeleton3DEditorPlugin() { skeleton_plugin = memnew(EditorInspectorPluginSkeleton); - skeleton_plugin->editor = editor; EditorInspector::add_inspector_plugin(skeleton_plugin); @@ -1025,10 +1138,10 @@ Skeleton3DEditorPlugin::Skeleton3DEditorPlugin(EditorNode *p_node) { Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); } -EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); Node3DEditor *ne = Node3DEditor::get_singleton(); - if (se->is_edit_mode()) { + if (se && se->is_edit_mode()) { const Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { if (ne->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { @@ -1040,7 +1153,7 @@ EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_spatial_gui_input(Ca se->update_bone_original(); } } - return EditorPlugin::AFTER_GUI_INPUT_DESELECT; + return EditorPlugin::AFTER_GUI_INPUT_CUSTOM; } return EditorPlugin::AFTER_GUI_INPUT_PASS; } @@ -1100,7 +1213,7 @@ void vertex() { COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); } VERTEX = VERTEX; - POSITION=PROJECTION_MATRIX*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0); + POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); POSITION.z = mix(POSITION.z, 0, 0.998); } void fragment() { @@ -1131,12 +1244,12 @@ int Skeleton3DGizmoPlugin::get_priority() const { } int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); ERR_FAIL_COND_V(!skeleton, -1); Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); - if (!se->is_edit_mode()) { + if (!se || !se->is_edit_mode()) { return -1; } @@ -1150,8 +1263,8 @@ int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gi Transform3D gt = skeleton->get_global_transform(); int closest_idx = -1; real_t closest_dist = 1e10; - const int bone_len = skeleton->get_bone_count(); - for (int i = 0; i < bone_len; i++) { + const int bone_count = skeleton->get_bone_count(); + for (int i = 0; i < bone_count; i++) { Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin); Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d); real_t dist_3d = ray_from.distance_to(joint_pos_3d); @@ -1163,8 +1276,6 @@ int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gi } if (closest_idx >= 0) { - WARN_PRINT("ray:"); - WARN_PRINT(itos(closest_idx)); se->select_bone(closest_idx); return closest_idx; } @@ -1174,33 +1285,32 @@ int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gi } Transform3D Skeleton3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); ERR_FAIL_COND_V(!skeleton, Transform3D()); return skeleton->get_bone_global_pose(p_id); } void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); ERR_FAIL_COND(!skeleton); // Prepare for global to local. - Transform3D original_to_local = Transform3D(); + Transform3D original_to_local; int parent_idx = skeleton->get_bone_parent(p_id); if (parent_idx >= 0) { - original_to_local = original_to_local * skeleton->get_bone_global_pose(parent_idx); + original_to_local = skeleton->get_bone_global_pose(parent_idx); } Basis to_local = original_to_local.get_basis().inverse(); // Prepare transform. - Transform3D t = Transform3D(); + Transform3D t; // Basis. t.basis = to_local * p_transform.get_basis(); // Origin. - Vector3 orig = Vector3(); - orig = skeleton->get_bone_pose(p_id).origin; + Vector3 orig = skeleton->get_bone_pose(p_id).origin; Vector3 sub = p_transform.origin - skeleton->get_bone_global_pose(p_id).origin; t.origin = orig + to_local.xform(sub); @@ -1211,13 +1321,13 @@ void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gi } void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); ERR_FAIL_COND(!skeleton); Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); Node3DEditor *ne = Node3DEditor::get_singleton(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Bone Transform")); if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) { for (int i = 0; i < p_ids.size(); i++) { @@ -1244,19 +1354,23 @@ void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, c } void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); p_gizmo->clear(); + if (!skeleton->get_bone_count()) { + return; + } + int selected = -1; Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); if (se) { selected = se->get_selected_bone(); } - Color bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/skeleton"); - Color selected_bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/selected_bone"); - real_t bone_axis_length = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_axis_length"); - int bone_shape = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_shape"); + Color bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/skeleton"); + Color selected_bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/selected_bone"); + real_t bone_axis_length = EDITOR_GET("editors/3d_gizmos/gizmo_settings/bone_axis_length"); + int bone_shape = EDITOR_GET("editors/3d_gizmos/gizmo_settings/bone_shape"); LocalVector<Color> axis_colors; axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); @@ -1273,9 +1387,6 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { surface_tool->set_material(unselected_mat); } - Vector<Transform3D> grests; - grests.resize(skeleton->get_bone_count()); - LocalVector<int> bones; LocalVector<float> weights; bones.resize(4); @@ -1299,11 +1410,6 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { child_bones_vector = skeleton->get_bone_children(current_bone_idx); int child_bones_size = child_bones_vector.size(); - // You have children but no parent, then you must be a root/parentless bone. - if (skeleton->get_bone_parent(current_bone_idx) < 0) { - grests.write[current_bone_idx] = skeleton->get_bone_rest(current_bone_idx); - } - for (int i = 0; i < child_bones_size; i++) { // Something wrong. if (child_bones_vector[i] < 0) { @@ -1312,10 +1418,8 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { int child_bone_idx = child_bones_vector[i]; - grests.write[child_bone_idx] = grests[current_bone_idx] * skeleton->get_bone_rest(child_bone_idx); - - Vector3 v0 = grests[current_bone_idx].origin; - Vector3 v1 = grests[child_bone_idx].origin; + Vector3 v0 = skeleton->get_bone_global_rest(current_bone_idx).origin; + Vector3 v1 = skeleton->get_bone_global_rest(child_bone_idx).origin; Vector3 d = (v1 - v0).normalized(); real_t dist = v0.distance_to(v1); @@ -1323,7 +1427,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { int closest = -1; real_t closest_d = 0.0; for (int j = 0; j < 3; j++) { - real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d)); + real_t dp = Math::abs(skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d)); if (j == 0 || dp > closest_d) { closest = j; } @@ -1350,7 +1454,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { for (int j = 0; j < 3; j++) { Vector3 axis; if (first == Vector3()) { - axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized(); + axis = d.cross(d.cross(skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized(); first = axis; } else { axis = d.cross(first).normalized(); @@ -1405,7 +1509,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { surface_tool->add_vertex(v0); surface_tool->set_bones(bones); surface_tool->set_weights(weights); - surface_tool->add_vertex(v0 + (grests[current_bone_idx].basis.inverse())[j].normalized() * dist * bone_axis_length); + surface_tool->add_vertex(v0 + (skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); if (j == closest) { continue; @@ -1422,7 +1526,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { surface_tool->add_vertex(v1); surface_tool->set_bones(bones); surface_tool->set_weights(weights); - surface_tool->add_vertex(v1 + (grests[child_bone_idx].basis.inverse())[j].normalized() * dist * bone_axis_length); + surface_tool->add_vertex(v1 + (skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); if (j == closest) { continue; diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index d0d81d6498..3eb840cfa9 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -1,37 +1,37 @@ -/*************************************************************************/ -/* skeleton_3d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* skeleton_3d_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 SKELETON_3D_EDITOR_PLUGIN_H #define SKELETON_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/editor_file_dialog.h" #include "editor/editor_plugin.h" #include "editor/editor_properties.h" #include "node_3d_editor_plugin.h" @@ -45,6 +45,9 @@ class Joint; class PhysicalBone3D; class Skeleton3DEditorPlugin; class Button; +class Tree; +class TreeItem; +class VSeparator; class BoneTransformEditor : public VBoxContainer { GDCLASS(BoneTransformEditor, VBoxContainer); @@ -61,11 +64,9 @@ class BoneTransformEditor : public VBoxContainer { Rect2 background_rects[5]; - Skeleton3D *skeleton; + Skeleton3D *skeleton = nullptr; // String property; - UndoRedo *undo_redo; - bool toggle_enabled = false; bool updating = false; @@ -97,11 +98,12 @@ class Skeleton3DEditor : public VBoxContainer { friend class Skeleton3DEditorPlugin; enum SkeletonOption { - SKELETON_OPTION_INIT_ALL_POSES, - SKELETON_OPTION_INIT_SELECTED_POSES, + SKELETON_OPTION_RESET_ALL_POSES, + SKELETON_OPTION_RESET_SELECTED_POSES, SKELETON_OPTION_ALL_POSES_TO_RESTS, SKELETON_OPTION_SELECTED_POSES_TO_RESTS, SKELETON_OPTION_CREATE_PHYSICAL_SKELETON, + SKELETON_OPTION_EXPORT_SKELETON_PROFILE, }; struct BoneInfo { @@ -109,31 +111,30 @@ class Skeleton3DEditor : public VBoxContainer { Transform3D relative_rest; // Relative to skeleton node. }; - EditorNode *editor; - EditorInspectorPluginSkeleton *editor_plugin; + EditorInspectorPluginSkeleton *editor_plugin = nullptr; - Skeleton3D *skeleton; + Skeleton3D *skeleton = nullptr; Tree *joint_tree = nullptr; BoneTransformEditor *rest_editor = nullptr; BoneTransformEditor *pose_editor = nullptr; - VSeparator *separator; + VSeparator *separator = nullptr; MenuButton *skeleton_options = nullptr; - Button *edit_mode_button; + Button *edit_mode_button = nullptr; bool edit_mode = false; - HBoxContainer *animation_hb; - Button *key_loc_button; - Button *key_rot_button; - Button *key_scale_button; - Button *key_insert_button; - Button *key_insert_all_button; + HBoxContainer *animation_hb = nullptr; + Button *key_loc_button = nullptr; + Button *key_rot_button = nullptr; + Button *key_scale_button = nullptr; + Button *key_insert_button = nullptr; + Button *key_insert_all_button = nullptr; EditorFileDialog *file_dialog = nullptr; - bool keyable; + bool keyable = false; static Skeleton3DEditor *singleton; @@ -149,7 +150,7 @@ class Skeleton3DEditor : public VBoxContainer { void create_editors(); - void init_pose(const bool p_all_bones); + void reset_pose(const bool p_all_bones); void pose_to_rest(const bool p_all_bones); void insert_keys(const bool p_all_bones); @@ -157,6 +158,8 @@ class Skeleton3DEditor : public VBoxContainer { void create_physical_skeleton(); PhysicalBone3D *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos); + void export_skeleton_profile(); + Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); @@ -165,7 +168,7 @@ class Skeleton3DEditor : public VBoxContainer { void set_bone_options_enabled(const bool p_bone_options_enabled); // Handle. - MeshInstance3D *handles_mesh_instance; + MeshInstance3D *handles_mesh_instance = nullptr; Ref<ImmediateMesh> handles_mesh; Ref<ShaderMaterial> handle_material; Ref<Shader> handle_shader; @@ -183,7 +186,7 @@ class Skeleton3DEditor : public VBoxContainer { void _draw_handles(); void _joint_tree_selection_changed(); - void _joint_tree_rmb_select(const Vector2 &p_pos); + void _joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button); void _update_properties(); void _subgizmo_selection_change(); @@ -213,7 +216,7 @@ public: Quaternion get_bone_original_rotation() const { return bone_original_rotation; }; Vector3 get_bone_original_scale() const { return bone_original_scale; }; - Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *skeleton); + Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, Skeleton3D *skeleton); ~Skeleton3DEditor(); }; @@ -222,8 +225,7 @@ class EditorInspectorPluginSkeleton : public EditorInspectorPlugin { friend class Skeleton3DEditorPlugin; - Skeleton3DEditor *skel_editor; - EditorNode *editor; + Skeleton3DEditor *skel_editor = nullptr; public: virtual bool can_handle(Object *p_object) override; @@ -233,18 +235,17 @@ public: class Skeleton3DEditorPlugin : public EditorPlugin { GDCLASS(Skeleton3DEditorPlugin, EditorPlugin); - EditorInspectorPluginSkeleton *skeleton_plugin; - EditorNode *editor; + EditorInspectorPluginSkeleton *skeleton_plugin = nullptr; public: - virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; + virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; bool has_main_screen() const override { return false; } virtual bool handles(Object *p_object) const override; virtual String get_name() const override { return "Skeleton3D"; } - Skeleton3DEditorPlugin(EditorNode *p_node); + Skeleton3DEditorPlugin(); }; class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin { diff --git a/editor/plugins/skeleton_ik_3d_editor_plugin.cpp b/editor/plugins/skeleton_ik_3d_editor_plugin.cpp index ca8786a385..1b78293a87 100644 --- a/editor/plugins/skeleton_ik_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_ik_3d_editor_plugin.cpp @@ -1,35 +1,36 @@ -/*************************************************************************/ -/* skeleton_ik_3d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* skeleton_ik_3d_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 "skeleton_ik_3d_editor_plugin.h" +#include "editor/editor_node.h" #include "scene/3d/skeleton_ik_3d.h" void SkeletonIK3DEditorPlugin::_play() { @@ -80,10 +81,9 @@ void SkeletonIK3DEditorPlugin::make_visible(bool p_visible) { void SkeletonIK3DEditorPlugin::_bind_methods() { } -SkeletonIK3DEditorPlugin::SkeletonIK3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +SkeletonIK3DEditorPlugin::SkeletonIK3DEditorPlugin() { play_btn = memnew(Button); - play_btn->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); + play_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); play_btn->set_text(TTR("Play IK")); play_btn->set_toggle_mode(true); play_btn->hide(); diff --git a/editor/plugins/skeleton_ik_3d_editor_plugin.h b/editor/plugins/skeleton_ik_3d_editor_plugin.h index edc3f6dda4..86d883f9fe 100644 --- a/editor/plugins/skeleton_ik_3d_editor_plugin.h +++ b/editor/plugins/skeleton_ik_3d_editor_plugin.h @@ -1,37 +1,36 @@ -/*************************************************************************/ -/* skeleton_ik_3d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* skeleton_ik_3d_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 SKELETON_IK_3D_EDITOR_PLUGIN_H #define SKELETON_IK_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" class SkeletonIK3D; @@ -39,10 +38,9 @@ class SkeletonIK3D; class SkeletonIK3DEditorPlugin : public EditorPlugin { GDCLASS(SkeletonIK3DEditorPlugin, EditorPlugin); - SkeletonIK3D *skeleton_ik; + SkeletonIK3D *skeleton_ik = nullptr; - Button *play_btn; - EditorNode *editor; + Button *play_btn = nullptr; void _play(); @@ -56,7 +54,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - SkeletonIK3DEditorPlugin(EditorNode *p_node); + SkeletonIK3DEditorPlugin(); ~SkeletonIK3DEditorPlugin(); }; diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index 1eac651ed6..0d00bfa8fd 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -1,43 +1,47 @@ -/*************************************************************************/ -/* sprite_2d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* sprite_2d_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 "sprite_2d_editor_plugin.h" #include "canvas_item_editor_plugin.h" #include "core/math/geometry_2d.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/scene_tree_dock.h" #include "scene/2d/collision_polygon_2d.h" #include "scene/2d/light_occluder_2d.h" #include "scene/2d/mesh_instance_2d.h" #include "scene/2d/polygon_2d.h" #include "scene/gui/box_container.h" +#include "scene/gui/menu_button.h" #include "thirdparty/misc/clipper.hpp" void Sprite2DEditor::_node_removed(Node *p_node) { @@ -120,44 +124,49 @@ void Sprite2DEditor::_menu_option(int p_option) { switch (p_option) { case MENU_OPTION_CONVERT_TO_MESH_2D: { - debug_uv_dialog->get_ok_button()->set_text(TTR("Create Mesh2D")); - debug_uv_dialog->set_title(TTR("Mesh2D Preview")); + debug_uv_dialog->set_ok_button_text(TTR("Create MeshInstance2D")); + debug_uv_dialog->set_title(TTR("MeshInstance2D Preview")); _update_mesh_data(); debug_uv_dialog->popup_centered(); - debug_uv->update(); + debug_uv->queue_redraw(); } break; case MENU_OPTION_CONVERT_TO_POLYGON_2D: { - debug_uv_dialog->get_ok_button()->set_text(TTR("Create Polygon2D")); + debug_uv_dialog->set_ok_button_text(TTR("Create Polygon2D")); debug_uv_dialog->set_title(TTR("Polygon2D Preview")); _update_mesh_data(); debug_uv_dialog->popup_centered(); - debug_uv->update(); + debug_uv->queue_redraw(); } break; case MENU_OPTION_CREATE_COLLISION_POLY_2D: { - debug_uv_dialog->get_ok_button()->set_text(TTR("Create CollisionPolygon2D")); + debug_uv_dialog->set_ok_button_text(TTR("Create CollisionPolygon2D")); debug_uv_dialog->set_title(TTR("CollisionPolygon2D Preview")); _update_mesh_data(); debug_uv_dialog->popup_centered(); - debug_uv->update(); + debug_uv->queue_redraw(); } break; case MENU_OPTION_CREATE_LIGHT_OCCLUDER_2D: { - debug_uv_dialog->get_ok_button()->set_text(TTR("Create LightOccluder2D")); + debug_uv_dialog->set_ok_button_text(TTR("Create LightOccluder2D")); debug_uv_dialog->set_title(TTR("LightOccluder2D Preview")); _update_mesh_data(); debug_uv_dialog->popup_centered(); - debug_uv->update(); + debug_uv->queue_redraw(); } break; } } void Sprite2DEditor::_update_mesh_data() { + if (node->get_owner() != get_tree()->get_edited_scene_root()) { + err_dialog->set_text(TTR("Can't convert a Sprite2D from a foreign scene.")); + err_dialog->popup_centered(); + } + Ref<Texture2D> texture = node->get_texture(); if (texture.is_null()) { err_dialog->set_text(TTR("Sprite2D is empty!")); @@ -294,7 +303,7 @@ void Sprite2DEditor::_update_mesh_data() { } } - debug_uv->update(); + debug_uv->queue_redraw(); } void Sprite2DEditor::_create_node() { @@ -335,11 +344,11 @@ void Sprite2DEditor::_convert_to_mesh_2d_node() { MeshInstance2D *mesh_instance = memnew(MeshInstance2D); mesh_instance->set_mesh(mesh); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Convert to Mesh2D")); - ur->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", node, mesh_instance, true, false); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Convert to MeshInstance2D")); + ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", node, mesh_instance, true, false); ur->add_do_reference(mesh_instance); - ur->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", mesh_instance, node, false, false); + ur->add_undo_method(SceneTreeDock::get_singleton(), "replace_node", mesh_instance, node, false, false); ur->add_undo_reference(node); ur->commit_action(); } @@ -393,11 +402,11 @@ void Sprite2DEditor::_convert_to_polygon_2d_node() { polygon_2d_instance->set_polygon(polygon); polygon_2d_instance->set_polygons(polys); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Convert to Polygon2D")); - ur->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", node, polygon_2d_instance, true, false); + ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", node, polygon_2d_instance, true, false); ur->add_do_reference(polygon_2d_instance); - ur->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", polygon_2d_instance, node, false, false); + ur->add_undo_method(SceneTreeDock::get_singleton(), "replace_node", polygon_2d_instance, node, false, false); ur->add_undo_reference(node); ur->commit_action(); } @@ -415,7 +424,7 @@ void Sprite2DEditor::_create_collision_polygon_2d_node() { CollisionPolygon2D *collision_polygon_2d_instance = memnew(CollisionPolygon2D); collision_polygon_2d_instance->set_polygon(outline); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Create CollisionPolygon2D Sibling")); ur->add_do_method(this, "_add_as_sibling_or_child", node, collision_polygon_2d_instance); ur->add_do_reference(collision_polygon_2d_instance); @@ -448,7 +457,7 @@ void Sprite2DEditor::_create_light_occluder_2d_node() { LightOccluder2D *light_occluder_2d_instance = memnew(LightOccluder2D); light_occluder_2d_instance->set_occluder_polygon(polygon); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Create LightOccluder2D Sibling")); ur->add_do_method(this, "_add_as_sibling_or_child", node, light_occluder_2d_instance); ur->add_do_reference(light_occluder_2d_instance); @@ -496,6 +505,20 @@ void Sprite2DEditor::_debug_uv_draw() { } } +void Sprite2DEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + options->set_icon(get_theme_icon(SNAME("Sprite2D"), SNAME("EditorIcons"))); + + options->get_popup()->set_item_icon(MENU_OPTION_CONVERT_TO_MESH_2D, get_theme_icon(SNAME("MeshInstance2D"), SNAME("EditorIcons"))); + options->get_popup()->set_item_icon(MENU_OPTION_CONVERT_TO_POLYGON_2D, get_theme_icon(SNAME("Polygon2D"), SNAME("EditorIcons"))); + options->get_popup()->set_item_icon(MENU_OPTION_CREATE_COLLISION_POLY_2D, get_theme_icon(SNAME("CollisionPolygon2D"), SNAME("EditorIcons"))); + options->get_popup()->set_item_icon(MENU_OPTION_CREATE_LIGHT_OCCLUDER_2D, get_theme_icon(SNAME("LightOccluder2D"), SNAME("EditorIcons"))); + } break; + } +} + void Sprite2DEditor::_bind_methods() { ClassDB::bind_method("_add_as_sibling_or_child", &Sprite2DEditor::_add_as_sibling_or_child); } @@ -506,9 +529,8 @@ Sprite2DEditor::Sprite2DEditor() { CanvasItemEditor::get_singleton()->add_control_to_menu_panel(options); options->set_text(TTR("Sprite2D")); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Sprite2D"), SNAME("EditorIcons"))); - options->get_popup()->add_item(TTR("Convert to Mesh2D"), MENU_OPTION_CONVERT_TO_MESH_2D); + options->get_popup()->add_item(TTR("Convert to MeshInstance2D"), MENU_OPTION_CONVERT_TO_MESH_2D); options->get_popup()->add_item(TTR("Convert to Polygon2D"), MENU_OPTION_CONVERT_TO_POLYGON_2D); options->get_popup()->add_item(TTR("Create CollisionPolygon2D Sibling"), MENU_OPTION_CREATE_COLLISION_POLY_2D); options->get_popup()->add_item(TTR("Create LightOccluder2D Sibling"), MENU_OPTION_CREATE_LIGHT_OCCLUDER_2D); @@ -520,8 +542,6 @@ Sprite2DEditor::Sprite2DEditor() { add_child(err_dialog); debug_uv_dialog = memnew(ConfirmationDialog); - debug_uv_dialog->get_ok_button()->set_text(TTR("Create Mesh2D")); - debug_uv_dialog->set_title(TTR("Mesh 2D Preview")); VBoxContainer *vb = memnew(VBoxContainer); debug_uv_dialog->add_child(vb); ScrollContainer *scroll = memnew(ScrollContainer); @@ -533,7 +553,7 @@ Sprite2DEditor::Sprite2DEditor() { debug_uv_dialog->connect("confirmed", callable_mp(this, &Sprite2DEditor::_create_node)); HBoxContainer *hb = memnew(HBoxContainer); - hb->add_child(memnew(Label(TTR("Simplification: ")))); + hb->add_child(memnew(Label(TTR("Simplification:")))); simplification = memnew(SpinBox); simplification->set_min(0.01); simplification->set_max(10.00); @@ -541,7 +561,7 @@ Sprite2DEditor::Sprite2DEditor() { simplification->set_value(2); hb->add_child(simplification); hb->add_spacer(); - hb->add_child(memnew(Label(TTR("Shrink (Pixels): ")))); + hb->add_child(memnew(Label(TTR("Shrink (Pixels):")))); shrink_pixels = memnew(SpinBox); shrink_pixels->set_min(0); shrink_pixels->set_max(10); @@ -549,7 +569,7 @@ Sprite2DEditor::Sprite2DEditor() { shrink_pixels->set_value(0); hb->add_child(shrink_pixels); hb->add_spacer(); - hb->add_child(memnew(Label(TTR("Grow (Pixels): ")))); + hb->add_child(memnew(Label(TTR("Grow (Pixels):")))); grow_pixels = memnew(SpinBox); grow_pixels->set_min(0); grow_pixels->set_max(10); @@ -583,10 +603,9 @@ void Sprite2DEditorPlugin::make_visible(bool p_visible) { } } -Sprite2DEditorPlugin::Sprite2DEditorPlugin(EditorNode *p_node) { - editor = p_node; +Sprite2DEditorPlugin::Sprite2DEditorPlugin() { sprite_editor = memnew(Sprite2DEditor); - editor->get_main_control()->add_child(sprite_editor); + EditorNode::get_singleton()->get_main_screen_control()->add_child(sprite_editor); make_visible(false); //sprite_editor->options->hide(); diff --git a/editor/plugins/sprite_2d_editor_plugin.h b/editor/plugins/sprite_2d_editor_plugin.h index c93ad1610f..4d351e2183 100644 --- a/editor/plugins/sprite_2d_editor_plugin.h +++ b/editor/plugins/sprite_2d_editor_plugin.h @@ -1,41 +1,44 @@ -/*************************************************************************/ -/* sprite_2d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 SPRITE_EDITOR_PLUGIN_H -#define SPRITE_EDITOR_PLUGIN_H - -#include "editor/editor_node.h" +/**************************************************************************/ +/* sprite_2d_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 SPRITE_2D_EDITOR_PLUGIN_H +#define SPRITE_2D_EDITOR_PLUGIN_H + #include "editor/editor_plugin.h" #include "scene/2d/sprite_2d.h" #include "scene/gui/spin_box.h" +class AcceptDialog; +class ConfirmationDialog; +class MenuButton; + class Sprite2DEditor : public Control { GDCLASS(Sprite2DEditor, Control); @@ -48,16 +51,16 @@ class Sprite2DEditor : public Control { Menu selected_menu_item; - Sprite2D *node; + Sprite2D *node = nullptr; - MenuButton *options; + MenuButton *options = nullptr; - ConfirmationDialog *outline_dialog; + ConfirmationDialog *outline_dialog = nullptr; - AcceptDialog *err_dialog; + AcceptDialog *err_dialog = nullptr; - ConfirmationDialog *debug_uv_dialog; - Control *debug_uv; + ConfirmationDialog *debug_uv_dialog = nullptr; + Control *debug_uv = nullptr; Vector<Vector2> uv_lines; Vector<Vector<Vector2>> outline_lines; Vector<Vector<Vector2>> computed_outline_lines; @@ -65,10 +68,10 @@ class Sprite2DEditor : public Control { Vector<Vector2> computed_uv; Vector<int> computed_indices; - SpinBox *simplification; - SpinBox *grow_pixels; - SpinBox *shrink_pixels; - Button *update_preview; + SpinBox *simplification = nullptr; + SpinBox *grow_pixels = nullptr; + SpinBox *shrink_pixels = nullptr; + Button *update_preview = nullptr; void _menu_option(int p_option); @@ -88,6 +91,7 @@ class Sprite2DEditor : public Control { protected: void _node_removed(Node *p_node); + void _notification(int p_what); static void _bind_methods(); public: @@ -98,8 +102,7 @@ public: class Sprite2DEditorPlugin : public EditorPlugin { GDCLASS(Sprite2DEditorPlugin, EditorPlugin); - Sprite2DEditor *sprite_editor; - EditorNode *editor; + Sprite2DEditor *sprite_editor = nullptr; public: virtual String get_name() const override { return "Sprite2D"; } @@ -108,8 +111,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - Sprite2DEditorPlugin(EditorNode *p_node); + Sprite2DEditorPlugin(); ~Sprite2DEditorPlugin(); }; -#endif // SPRITE_EDITOR_PLUGIN_H +#endif // SPRITE_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 460eb994e5..c41bf4b8cc 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -1,46 +1,53 @@ -/*************************************************************************/ -/* sprite_frames_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* sprite_frames_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 "sprite_frames_editor_plugin.h" #include "core/config/project_settings.h" #include "core/io/resource_loader.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_file_system.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" -#include "scene/3d/sprite_3d.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/scene_tree_dock.h" #include "scene/gui/center_container.h" #include "scene/gui/margin_container.h" #include "scene/gui/panel_container.h" +#include "scene/gui/separator.h" -void SpriteFramesEditor::gui_input(const Ref<InputEvent> &p_event) { +static void _draw_shadowed_line(Control *p_control, const Point2 &p_from, const Size2 &p_size, const Size2 &p_shadow_offset, Color p_color, Color p_shadow_color) { + p_control->draw_line(p_from, p_from + p_size, p_color); + p_control->draw_line(p_from + p_shadow_offset, p_from + p_size + p_shadow_offset, p_shadow_color); } void SpriteFramesEditor::_open_sprite_sheet() { @@ -55,73 +62,91 @@ void SpriteFramesEditor::_open_sprite_sheet() { } int SpriteFramesEditor::_sheet_preview_position_to_frame_index(const Point2 &p_position) { - if (p_position.x < 0 || p_position.y < 0) { - return -1; + const Size2i offset = _get_offset(); + const Size2i frame_size = _get_frame_size(); + const Size2i separation = _get_separation(); + const Size2i block_size = frame_size + separation; + const Point2i position = p_position / sheet_zoom - offset; + + if (position.x < 0 || position.y < 0) { + return -1; // Out of bounds. } - Size2i texture_size = split_sheet_preview->get_texture()->get_size(); - int h = split_sheet_h->get_value(); - int v = split_sheet_v->get_value(); - if (h > texture_size.width || v > texture_size.height) { - return -1; + if (position.x % block_size.x >= frame_size.x || position.y % block_size.y >= frame_size.y) { + return -1; // Gap between frames. } - int x = int(p_position.x / sheet_zoom) / (texture_size.width / h); - int y = int(p_position.y / sheet_zoom) / (texture_size.height / v); - if (x >= h || y >= v) { - return -1; + const Point2i frame = position / block_size; + const Size2i frame_count = _get_frame_count(); + if (frame.x >= frame_count.x || frame.y >= frame_count.y) { + return -1; // Out of bounds. } - return h * y + x; + + return frame_count.x * frame.y + frame.x; } void SpriteFramesEditor::_sheet_preview_draw() { - Size2i texture_size = split_sheet_preview->get_texture()->get_size(); - int h = split_sheet_h->get_value(); - int v = split_sheet_v->get_value(); - - real_t width = (texture_size.width / h) * sheet_zoom; - real_t height = (texture_size.height / v) * sheet_zoom; - const float a = 0.3; - - real_t y_end = v * height; - for (int i = 0; i <= h; i++) { - real_t x = i * width; - split_sheet_preview->draw_line(Point2(x, 0), Point2(x, y_end), Color(1, 1, 1, a)); - split_sheet_preview->draw_line(Point2(x + 1, 0), Point2(x + 1, y_end), Color(0, 0, 0, a)); + const Size2i frame_count = _get_frame_count(); + const Size2i separation = _get_separation(); + + const Size2 draw_offset = Size2(_get_offset()) * sheet_zoom; + const Size2 draw_sep = Size2(separation) * sheet_zoom; + const Size2 draw_frame_size = Size2(_get_frame_size()) * sheet_zoom; + const Size2 draw_size = draw_frame_size * frame_count + draw_sep * (frame_count - Size2i(1, 1)); + + const Color line_color = Color(1, 1, 1, 0.3); + const Color shadow_color = Color(0, 0, 0, 0.3); + + // Vertical lines. + _draw_shadowed_line(split_sheet_preview, draw_offset, Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color); + for (int i = 0; i < frame_count.x - 1; i++) { + const Point2 start = draw_offset + Vector2(i * draw_sep.x + (i + 1) * draw_frame_size.x, 0); + if (separation.x == 0) { + _draw_shadowed_line(split_sheet_preview, start, Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color); + } else { + const Size2 size = Size2(draw_sep.x, draw_size.y); + split_sheet_preview->draw_rect(Rect2(start, size), line_color); + } } - real_t x_end = h * width; - for (int i = 0; i <= v; i++) { - real_t y = i * height; - split_sheet_preview->draw_line(Point2(0, y), Point2(x_end, y), Color(1, 1, 1, a)); - split_sheet_preview->draw_line(Point2(0, y + 1), Point2(x_end, y + 1), Color(0, 0, 0, a)); + _draw_shadowed_line(split_sheet_preview, draw_offset + Vector2(draw_size.x, 0), Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color); + + // Horizontal lines. + _draw_shadowed_line(split_sheet_preview, draw_offset, Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color); + for (int i = 0; i < frame_count.y - 1; i++) { + const Point2 start = draw_offset + Vector2(0, i * draw_sep.y + (i + 1) * draw_frame_size.y); + if (separation.y == 0) { + _draw_shadowed_line(split_sheet_preview, start, Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color); + } else { + const Size2 size = Size2(draw_size.x, draw_sep.y); + split_sheet_preview->draw_rect(Rect2(start, size), line_color); + } } + _draw_shadowed_line(split_sheet_preview, draw_offset + Vector2(0, draw_size.y), Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color); if (frames_selected.size() == 0) { split_sheet_dialog->get_ok_button()->set_disabled(true); - split_sheet_dialog->get_ok_button()->set_text(TTR("No Frames Selected")); + split_sheet_dialog->set_ok_button_text(TTR("No Frames Selected")); return; } - Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - - for (Set<int>::Element *E = frames_selected.front(); E; E = E->next()) { - int idx = E->get(); - int xp = idx % h; - int yp = idx / h; - real_t x = xp * width; - real_t y = yp * height; + Color accent = get_theme_color("accent_color", "Editor"); - split_sheet_preview->draw_rect(Rect2(x + 5, y + 5, width - 10, height - 10), Color(0, 0, 0, 0.35), true); - split_sheet_preview->draw_rect(Rect2(x + 0, y + 0, width - 0, height - 0), Color(0, 0, 0, 1), false); - split_sheet_preview->draw_rect(Rect2(x + 1, y + 1, width - 2, height - 2), Color(0, 0, 0, 1), false); - split_sheet_preview->draw_rect(Rect2(x + 2, y + 2, width - 4, height - 4), accent, false); - split_sheet_preview->draw_rect(Rect2(x + 3, y + 3, width - 6, height - 6), accent, false); - split_sheet_preview->draw_rect(Rect2(x + 4, y + 4, width - 8, height - 8), Color(0, 0, 0, 1), false); - split_sheet_preview->draw_rect(Rect2(x + 5, y + 5, width - 10, height - 10), Color(0, 0, 0, 1), false); + for (const int &E : frames_selected) { + const int idx = E; + const int x = idx % frame_count.x; + const int y = idx / frame_count.x; + const Point2 pos = draw_offset + Point2(x, y) * (draw_frame_size + draw_sep); + split_sheet_preview->draw_rect(Rect2(pos + Size2(5, 5), draw_frame_size - Size2(10, 10)), Color(0, 0, 0, 0.35), true); + split_sheet_preview->draw_rect(Rect2(pos, draw_frame_size), Color(0, 0, 0, 1), false); + split_sheet_preview->draw_rect(Rect2(pos + Size2(1, 1), draw_frame_size - Size2(2, 2)), Color(0, 0, 0, 1), false); + split_sheet_preview->draw_rect(Rect2(pos + Size2(2, 2), draw_frame_size - Size2(4, 4)), accent, false); + split_sheet_preview->draw_rect(Rect2(pos + Size2(3, 3), draw_frame_size - Size2(6, 6)), accent, false); + split_sheet_preview->draw_rect(Rect2(pos + Size2(4, 4), draw_frame_size - Size2(8, 8)), Color(0, 0, 0, 1), false); + split_sheet_preview->draw_rect(Rect2(pos + Size2(5, 5), draw_frame_size - Size2(10, 10)), Color(0, 0, 0, 1), false); } split_sheet_dialog->get_ok_button()->set_disabled(false); - split_sheet_dialog->get_ok_button()->set_text(vformat(TTR("Add %d Frame(s)"), frames_selected.size())); + split_sheet_dialog->set_ok_button_text(vformat(TTR("Add %d Frame(s)"), frames_selected.size())); } void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) { @@ -162,7 +187,7 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) { if (last_frame_selected != idx || idx != -1) { last_frame_selected = idx; - split_sheet_preview->update(); + split_sheet_preview->queue_redraw(); } } @@ -171,7 +196,7 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) { } const Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { + if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { // Select by holding down the mouse button on frames. const int idx = _sheet_preview_position_to_frame_index(mm->get_position()); @@ -188,7 +213,7 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) { } last_frame_selected = idx; - split_sheet_preview->update(); + split_sheet_preview->queue_redraw(); } } } @@ -201,52 +226,45 @@ void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) { // to allow performing this action anywhere, even if the cursor isn't // hovering the texture in the workspace. if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) { - _sheet_zoom_in(); + _sheet_zoom_on_position(scale_ratio, mb->get_position()); // Don't scroll up after zooming in. - accept_event(); + split_sheet_scroll->accept_event(); } else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) { - _sheet_zoom_out(); + _sheet_zoom_on_position(1 / scale_ratio, mb->get_position()); // Don't scroll down after zooming out. - accept_event(); + split_sheet_scroll->accept_event(); } } + + const Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) { + const Vector2 dragged = Input::get_singleton()->warp_mouse_motion(mm, split_sheet_scroll->get_global_rect()); + split_sheet_scroll->set_h_scroll(split_sheet_scroll->get_h_scroll() - dragged.x); + split_sheet_scroll->set_v_scroll(split_sheet_scroll->get_v_scroll() - dragged.y); + } } void SpriteFramesEditor::_sheet_add_frames() { - Size2i texture_size = split_sheet_preview->get_texture()->get_size(); - int frame_count_x = split_sheet_h->get_value(); - int frame_count_y = split_sheet_v->get_value(); - Size2 frame_size(texture_size.width / frame_count_x, texture_size.height / frame_count_y); - - undo_redo->create_action(TTR("Add Frame")); + const Size2i frame_count = _get_frame_count(); + const Size2i frame_size = _get_frame_size(); + const Size2i offset = _get_offset(); + const Size2i separation = _get_separation(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); int fc = frames->get_frame_count(edited_anim); - Point2 src_origin; - Rect2 src_region(Point2(), texture_size); - - AtlasTexture *src_atlas = Object::cast_to<AtlasTexture>(*split_sheet_preview->get_texture()); - if (src_atlas && src_atlas->get_atlas().is_valid()) { - src_origin = src_atlas->get_region().position - src_atlas->get_margin().position; - src_region = src_atlas->get_region(); - } - - for (Set<int>::Element *E = frames_selected.front(); E; E = E->next()) { - int idx = E->get(); - Point2 frame_coords(idx % frame_count_x, idx / frame_count_x); - - Rect2 frame(frame_coords * frame_size + src_origin, frame_size); - Rect2 region = frame.intersection(src_region); - Rect2 margin(region == Rect2() ? Point2() : region.position - frame.position, frame.size - region.size); + for (const int &E : frames_selected) { + int idx = E; + const Point2 frame_coords(idx % frame_count.x, idx / frame_count.x); Ref<AtlasTexture> at; at.instantiate(); at->set_atlas(split_sheet_preview->get_texture()); - at->set_region(region); - at->set_margin(margin); + at->set_region(Rect2(offset + frame_coords * (frame_size + separation), frame_size)); - undo_redo->add_do_method(frames, "add_frame", edited_anim, at, -1); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, fc); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, at, 1.0, -1); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, fc); } undo_redo->add_do_method(this, "_update_library"); @@ -254,20 +272,25 @@ void SpriteFramesEditor::_sheet_add_frames() { undo_redo->commit_action(); } +void SpriteFramesEditor::_sheet_zoom_on_position(float p_zoom, const Vector2 &p_position) { + const float old_zoom = sheet_zoom; + sheet_zoom = CLAMP(sheet_zoom * p_zoom, min_sheet_zoom, max_sheet_zoom); + + const Size2 texture_size = split_sheet_preview->get_texture()->get_size(); + split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom); + + Vector2 offset = Vector2(split_sheet_scroll->get_h_scroll(), split_sheet_scroll->get_v_scroll()); + offset = (offset + p_position) / old_zoom * sheet_zoom - p_position; + split_sheet_scroll->set_h_scroll(offset.x); + split_sheet_scroll->set_v_scroll(offset.y); +} + void SpriteFramesEditor::_sheet_zoom_in() { - if (sheet_zoom < max_sheet_zoom) { - sheet_zoom *= scale_ratio; - Size2 texture_size = split_sheet_preview->get_texture()->get_size(); - split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom); - } + _sheet_zoom_on_position(scale_ratio, Vector2()); } void SpriteFramesEditor::_sheet_zoom_out() { - if (sheet_zoom > min_sheet_zoom) { - sheet_zoom /= scale_ratio; - Size2 texture_size = split_sheet_preview->get_texture()->get_size(); - split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom); - } + _sheet_zoom_on_position(1 / scale_ratio, Vector2()); } void SpriteFramesEditor::_sheet_zoom_reset() { @@ -289,20 +312,70 @@ void SpriteFramesEditor::_sheet_select_clear_all_frames() { frames_selected.clear(); } - split_sheet_preview->update(); + split_sheet_preview->queue_redraw(); } -void SpriteFramesEditor::_sheet_spin_changed(double) { +void SpriteFramesEditor::_sheet_spin_changed(double p_value, int p_dominant_param) { + if (updating_split_settings) { + return; + } + updating_split_settings = true; + + if (p_dominant_param != PARAM_USE_CURRENT) { + dominant_param = p_dominant_param; + } + + const Size2i texture_size = split_sheet_preview->get_texture()->get_size(); + const Size2i size = texture_size - _get_offset(); + + switch (dominant_param) { + case PARAM_SIZE: { + const Size2i frame_size = _get_frame_size(); + + const Size2i offset_max = texture_size - frame_size; + split_sheet_offset_x->set_max(offset_max.x); + split_sheet_offset_y->set_max(offset_max.y); + + const Size2i sep_max = size - frame_size * 2; + split_sheet_sep_x->set_max(sep_max.x); + split_sheet_sep_y->set_max(sep_max.y); + + const Size2i separation = _get_separation(); + const Size2i count = (size + separation) / (frame_size + separation); + split_sheet_h->set_value(count.x); + split_sheet_v->set_value(count.y); + } break; + + case PARAM_FRAME_COUNT: { + const Size2i count = _get_frame_count(); + + const Size2i offset_max = texture_size - count; + split_sheet_offset_x->set_max(offset_max.x); + split_sheet_offset_y->set_max(offset_max.y); + + const Size2i gap_count = count - Size2i(1, 1); + split_sheet_sep_x->set_max(gap_count.x == 0 ? size.x : (size.x - count.x) / gap_count.x); + split_sheet_sep_y->set_max(gap_count.y == 0 ? size.y : (size.y - count.y) / gap_count.y); + + const Size2i separation = _get_separation(); + const Size2i frame_size = (size - separation * gap_count) / count; + split_sheet_size_x->set_value(frame_size.x); + split_sheet_size_y->set_value(frame_size.y); + } break; + } + + updating_split_settings = false; + frames_selected.clear(); last_frame_selected = -1; - split_sheet_preview->update(); + split_sheet_preview->queue_redraw(); } void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) { - Ref<Resource> texture = ResourceLoader::load(p_file); - if (!texture.is_valid()) { + Ref<Texture2D> texture = ResourceLoader::load(p_file); + if (texture.is_null()) { EditorNode::get_singleton()->show_warning(TTR("Unable to load images")); - ERR_FAIL_COND(!texture.is_valid()); + ERR_FAIL_COND(texture.is_null()); } frames_selected.clear(); last_frame_selected = -1; @@ -310,10 +383,29 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) { bool new_texture = texture != split_sheet_preview->get_texture(); split_sheet_preview->set_texture(texture); if (new_texture) { - //different texture, reset to 4x4 + // Reset spin max. + const Size2i size = texture->get_size(); + split_sheet_size_x->set_max(size.x); + split_sheet_size_y->set_max(size.y); + split_sheet_sep_x->set_max(size.x); + split_sheet_sep_y->set_max(size.y); + split_sheet_offset_x->set_max(size.x); + split_sheet_offset_y->set_max(size.y); + + // Different texture, reset to 4x4. + dominant_param = PARAM_FRAME_COUNT; + updating_split_settings = true; split_sheet_h->set_value(4); split_sheet_v->set_value(4); - //reset zoom + split_sheet_size_x->set_value(size.x / 4); + split_sheet_size_y->set_value(size.y / 4); + split_sheet_sep_x->set_value(0); + split_sheet_sep_y->set_value(0); + split_sheet_offset_x->set_value(0); + split_sheet_offset_y->set_value(0); + updating_split_settings = false; + + // Reset zoom. _sheet_zoom_reset(); } split_sheet_dialog->popup_centered_ratio(0.65); @@ -322,31 +414,51 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) { void SpriteFramesEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", callable_mp(this, &SpriteFramesEditor::_node_removed)); + + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + autoplay_icon = get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons")); + stop_icon = get_theme_icon(SNAME("Stop"), SNAME("EditorIcons")); + pause_icon = get_theme_icon(SNAME("Pause"), SNAME("EditorIcons")); + _update_stop_icon(); + + autoplay->set_icon(get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons"))); + anim_loop->set_icon(get_theme_icon(SNAME("Loop"), SNAME("EditorIcons"))); + play->set_icon(get_theme_icon(SNAME("PlayStart"), SNAME("EditorIcons"))); + play_from->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); + play_bw->set_icon(get_theme_icon(SNAME("PlayStartBackwards"), SNAME("EditorIcons"))); + play_bw_from->set_icon(get_theme_icon(SNAME("PlayBackwards"), SNAME("EditorIcons"))); + load->set_icon(get_theme_icon(SNAME("Load"), SNAME("EditorIcons"))); load_sheet->set_icon(get_theme_icon(SNAME("SpriteSheet"), SNAME("EditorIcons"))); copy->set_icon(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons"))); paste->set_icon(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons"))); - empty->set_icon(get_theme_icon(SNAME("InsertBefore"), SNAME("EditorIcons"))); - empty2->set_icon(get_theme_icon(SNAME("InsertAfter"), SNAME("EditorIcons"))); + empty_before->set_icon(get_theme_icon(SNAME("InsertBefore"), SNAME("EditorIcons"))); + empty_after->set_icon(get_theme_icon(SNAME("InsertAfter"), SNAME("EditorIcons"))); move_up->set_icon(get_theme_icon(SNAME("MoveLeft"), SNAME("EditorIcons"))); move_down->set_icon(get_theme_icon(SNAME("MoveRight"), SNAME("EditorIcons"))); - _delete->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + delete_frame->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); zoom_out->set_icon(get_theme_icon(SNAME("ZoomLess"), SNAME("EditorIcons"))); zoom_reset->set_icon(get_theme_icon(SNAME("ZoomReset"), SNAME("EditorIcons"))); zoom_in->set_icon(get_theme_icon(SNAME("ZoomMore"), SNAME("EditorIcons"))); - new_anim->set_icon(get_theme_icon(SNAME("New"), SNAME("EditorIcons"))); - remove_anim->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + add_anim->set_icon(get_theme_icon(SNAME("New"), SNAME("EditorIcons"))); + delete_anim->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + anim_search_box->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); split_sheet_zoom_out->set_icon(get_theme_icon(SNAME("ZoomLess"), SNAME("EditorIcons"))); split_sheet_zoom_reset->set_icon(get_theme_icon(SNAME("ZoomReset"), SNAME("EditorIcons"))); split_sheet_zoom_in->set_icon(get_theme_icon(SNAME("ZoomMore"), SNAME("EditorIcons"))); - [[fallthrough]]; - } - case NOTIFICATION_THEME_CHANGED: { - split_sheet_scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + split_sheet_scroll->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); } break; + case NOTIFICATION_READY: { add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up. } break; + + case NOTIFICATION_EXIT_TREE: { + get_tree()->disconnect("node_removed", callable_mp(this, &SpriteFramesEditor::_node_removed)); + } break; } } @@ -364,7 +476,7 @@ void SpriteFramesEditor::_file_load_request(const Vector<String> &p_path, int p_ dialog->set_title(TTR("Error!")); //dialog->get_cancel()->set_text("Close"); - dialog->get_ok_button()->set_text(TTR("Close")); + dialog->set_ok_button_text(TTR("Close")); dialog->popup_centered(); return; ///beh should show an error i guess } @@ -376,14 +488,15 @@ void SpriteFramesEditor::_file_load_request(const Vector<String> &p_path, int p_ return; } - undo_redo->create_action(TTR("Add Frame")); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); int fc = frames->get_frame_count(edited_anim); int count = 0; for (const Ref<Texture2D> &E : resources) { - undo_redo->add_do_method(frames, "add_frame", edited_anim, E, p_at_pos == -1 ? -1 : p_at_pos + count); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, p_at_pos == -1 ? fc : p_at_pos); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, E, 1.0, p_at_pos == -1 ? -1 : p_at_pos + count); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, p_at_pos == -1 ? fc : p_at_pos); count++; } undo_redo->add_do_method(this, "_update_library"); @@ -392,6 +505,22 @@ void SpriteFramesEditor::_file_load_request(const Vector<String> &p_path, int p_ undo_redo->commit_action(); } +Size2i SpriteFramesEditor::_get_frame_count() const { + return Size2i(split_sheet_h->get_value(), split_sheet_v->get_value()); +} + +Size2i SpriteFramesEditor::_get_frame_size() const { + return Size2i(split_sheet_size_x->get_value(), split_sheet_size_y->get_value()); +} + +Size2i SpriteFramesEditor::_get_offset() const { + return Size2i(split_sheet_offset_x->get_value(), split_sheet_offset_y->get_value()); +} + +Size2i SpriteFramesEditor::_get_separation() const { + return Size2i(split_sheet_sep_x->get_value(), split_sheet_sep_y->get_value()); +} + void SpriteFramesEditor::_load_pressed() { ERR_FAIL_COND(!frames->has_animation(edited_anim)); loading_scene = false; @@ -410,19 +539,30 @@ void SpriteFramesEditor::_load_pressed() { void SpriteFramesEditor::_paste_pressed() { ERR_FAIL_COND(!frames->has_animation(edited_anim)); - Ref<Texture2D> r = EditorSettings::get_singleton()->get_resource_clipboard(); - if (!r.is_valid()) { + Ref<Texture2D> texture; + float duration = 1.0; + + Ref<EditorSpriteFramesFrame> frame = EditorSettings::get_singleton()->get_resource_clipboard(); + if (frame.is_valid()) { + texture = frame->texture; + duration = frame->duration; + } else { + texture = EditorSettings::get_singleton()->get_resource_clipboard(); + } + + if (texture.is_null()) { dialog->set_text(TTR("Resource clipboard is empty or not a texture!")); dialog->set_title(TTR("Error!")); //dialog->get_cancel()->set_text("Close"); - dialog->get_ok_button()->set_text(TTR("Close")); + dialog->set_ok_button_text(TTR("Close")); dialog->popup_centered(); return; ///beh should show an error i guess } - undo_redo->create_action(TTR("Paste Frame")); - undo_redo->add_do_method(frames, "add_frame", edited_anim, r); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, frames->get_frame_count(edited_anim)); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Paste Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, duration); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, frames->get_frame_count(edited_anim)); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -431,15 +571,20 @@ void SpriteFramesEditor::_paste_pressed() { void SpriteFramesEditor::_copy_pressed() { ERR_FAIL_COND(!frames->has_animation(edited_anim)); - if (tree->get_current() < 0) { + if (frame_list->get_current() < 0) { return; } - Ref<Texture2D> r = frames->get_frame(edited_anim, tree->get_current()); - if (!r.is_valid()) { + + Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, frame_list->get_current()); + if (texture.is_null()) { return; } - EditorSettings::get_singleton()->set_resource_clipboard(r); + Ref<EditorSpriteFramesFrame> frame = memnew(EditorSpriteFramesFrame); + frame->texture = texture; + frame->duration = frames->get_frame_duration(edited_anim, frame_list->get_current()); + + EditorSettings::get_singleton()->set_resource_clipboard(frame); } void SpriteFramesEditor::_empty_pressed() { @@ -447,19 +592,20 @@ void SpriteFramesEditor::_empty_pressed() { int from = -1; - if (tree->get_current() >= 0) { - from = tree->get_current(); + if (frame_list->get_current() >= 0) { + from = frame_list->get_current(); sel = from; } else { from = frames->get_frame_count(edited_anim); } - Ref<Texture2D> r; + Ref<Texture2D> texture; - undo_redo->create_action(TTR("Add Empty")); - undo_redo->add_do_method(frames, "add_frame", edited_anim, r, from); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, from); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Add Empty"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, from); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, from); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -470,19 +616,20 @@ void SpriteFramesEditor::_empty2_pressed() { int from = -1; - if (tree->get_current() >= 0) { - from = tree->get_current(); + if (frame_list->get_current() >= 0) { + from = frame_list->get_current(); sel = from; } else { from = frames->get_frame_count(edited_anim); } - Ref<Texture2D> r; + Ref<Texture2D> texture; - undo_redo->create_action(TTR("Add Empty")); - undo_redo->add_do_method(frames, "add_frame", edited_anim, r, from + 1); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, from + 1); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Add Empty"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, from + 1); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, from + 1); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -491,11 +638,11 @@ void SpriteFramesEditor::_empty2_pressed() { void SpriteFramesEditor::_up_pressed() { ERR_FAIL_COND(!frames->has_animation(edited_anim)); - if (tree->get_current() < 0) { + if (frame_list->get_current() < 0) { return; } - int to_move = tree->get_current(); + int to_move = frame_list->get_current(); if (to_move < 1) { return; } @@ -503,11 +650,12 @@ void SpriteFramesEditor::_up_pressed() { sel = to_move; sel -= 1; - undo_redo->create_action(TTR("Delete Resource")); - undo_redo->add_do_method(frames, "set_frame", edited_anim, to_move, frames->get_frame(edited_anim, to_move - 1)); - undo_redo->add_do_method(frames, "set_frame", edited_anim, to_move - 1, frames->get_frame(edited_anim, to_move)); - undo_redo->add_undo_method(frames, "set_frame", edited_anim, to_move, frames->get_frame(edited_anim, to_move)); - undo_redo->add_undo_method(frames, "set_frame", edited_anim, to_move - 1, frames->get_frame(edited_anim, to_move - 1)); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move - 1), frames->get_frame_duration(edited_anim, to_move - 1)); + undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, to_move - 1, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move)); + undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move)); + undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, to_move - 1, frames->get_frame_texture(edited_anim, to_move - 1), frames->get_frame_duration(edited_anim, to_move - 1)); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -516,11 +664,11 @@ void SpriteFramesEditor::_up_pressed() { void SpriteFramesEditor::_down_pressed() { ERR_FAIL_COND(!frames->has_animation(edited_anim)); - if (tree->get_current() < 0) { + if (frame_list->get_current() < 0) { return; } - int to_move = tree->get_current(); + int to_move = frame_list->get_current(); if (to_move < 0 || to_move >= frames->get_frame_count(edited_anim) - 1) { return; } @@ -528,11 +676,12 @@ void SpriteFramesEditor::_down_pressed() { sel = to_move; sel += 1; - undo_redo->create_action(TTR("Delete Resource")); - undo_redo->add_do_method(frames, "set_frame", edited_anim, to_move, frames->get_frame(edited_anim, to_move + 1)); - undo_redo->add_do_method(frames, "set_frame", edited_anim, to_move + 1, frames->get_frame(edited_anim, to_move)); - undo_redo->add_undo_method(frames, "set_frame", edited_anim, to_move, frames->get_frame(edited_anim, to_move)); - undo_redo->add_undo_method(frames, "set_frame", edited_anim, to_move + 1, frames->get_frame(edited_anim, to_move + 1)); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move + 1), frames->get_frame_duration(edited_anim, to_move + 1)); + undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, to_move + 1, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move)); + undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move)); + undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, to_move + 1, frames->get_frame_texture(edited_anim, to_move + 1), frames->get_frame_duration(edited_anim, to_move + 1)); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -541,24 +690,25 @@ void SpriteFramesEditor::_down_pressed() { void SpriteFramesEditor::_delete_pressed() { ERR_FAIL_COND(!frames->has_animation(edited_anim)); - if (tree->get_current() < 0) { + if (frame_list->get_current() < 0) { return; } - int to_delete = tree->get_current(); + int to_delete = frame_list->get_current(); if (to_delete < 0 || to_delete >= frames->get_frame_count(edited_anim)) { return; } - undo_redo->create_action(TTR("Delete Resource")); - undo_redo->add_do_method(frames, "remove_frame", edited_anim, to_delete); - undo_redo->add_undo_method(frames, "add_frame", edited_anim, frames->get_frame(edited_anim, to_delete), to_delete); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Delete Resource"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "remove_frame", edited_anim, to_delete); + undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, frames->get_frame_texture(edited_anim, to_delete), frames->get_frame_duration(edited_anim, to_delete), to_delete); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); } -void SpriteFramesEditor::_animation_select() { +void SpriteFramesEditor::_animation_selected() { if (updating) { return; } @@ -566,16 +716,49 @@ void SpriteFramesEditor::_animation_select() { if (frames->has_animation(edited_anim)) { double value = anim_speed->get_line_edit()->get_text().to_float(); if (!Math::is_equal_approx(value, (double)frames->get_animation_speed(edited_anim))) { - _animation_fps_changed(value); + _animation_speed_changed(value); } } TreeItem *selected = animations->get_selected(); ERR_FAIL_COND(!selected); edited_anim = selected->get_text(0); + + if (animated_sprite) { + sprite_node_updating = true; + animated_sprite->call("set_animation", edited_anim); + sprite_node_updating = false; + } + _update_library(true); } +void SpriteFramesEditor::_sync_animation() { + if (!animated_sprite || sprite_node_updating) { + return; + } + _select_animation(animated_sprite->call("get_animation"), false); + _update_stop_icon(); +} + +void SpriteFramesEditor::_select_animation(const String &p_name, bool p_update_node) { + TreeItem *selected = nullptr; + selected = animations->get_item_with_text(p_name); + if (!selected) { + return; + }; + + edited_anim = selected->get_text(0); + + if (animated_sprite) { + if (p_update_node) { + animated_sprite->call("set_animation", edited_anim); + } + } + + _update_library(); +} + static void _find_anim_sprites(Node *p_node, List<Node *> *r_nodes, Ref<SpriteFrames> p_sfames) { Node *edited = EditorNode::get_singleton()->get_edited_scene(); if (!edited) { @@ -633,53 +816,67 @@ void SpriteFramesEditor::_animation_name_edited() { name = new_name + " " + itos(counter); } - List<Node *> nodes; - _find_anim_sprites(EditorNode::get_singleton()->get_edited_scene(), &nodes, Ref<SpriteFrames>(frames)); - - undo_redo->create_action(TTR("Rename Animation")); - undo_redo->add_do_method(frames, "rename_animation", edited_anim, name); - undo_redo->add_undo_method(frames, "rename_animation", name, edited_anim); - - for (Node *E : nodes) { - String current = E->call("get_animation"); - undo_redo->add_do_method(E, "set_animation", name); - undo_redo->add_undo_method(E, "set_animation", edited_anim); - } - + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Rename Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + _rename_node_animation(undo_redo, false, edited_anim, "", ""); + undo_redo->add_do_method(frames.ptr(), "rename_animation", edited_anim, name); + undo_redo->add_undo_method(frames.ptr(), "rename_animation", name, edited_anim); + _rename_node_animation(undo_redo, false, edited_anim, name, name); + _rename_node_animation(undo_redo, true, edited_anim, edited_anim, edited_anim); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); + undo_redo->commit_action(); - edited_anim = new_name; + _select_animation(name); + animations->grab_focus(); +} - undo_redo->commit_action(); +void SpriteFramesEditor::_rename_node_animation(EditorUndoRedoManager *undo_redo, bool is_undo, const String &p_filter, const String &p_new_animation, const String &p_new_autoplay) { + List<Node *> nodes; + _find_anim_sprites(EditorNode::get_singleton()->get_edited_scene(), &nodes, Ref<SpriteFrames>(frames)); + + if (is_undo) { + for (Node *E : nodes) { + String current_name = E->call("get_animation"); + if (current_name == p_filter) { + undo_redo->add_undo_method(E, "set_animation", p_new_animation); + } + String autoplay_name = E->call("get_autoplay"); + if (autoplay_name == p_filter) { + undo_redo->add_undo_method(E, "set_autoplay", p_new_autoplay); + } + } + } else { + for (Node *E : nodes) { + String current_name = E->call("get_animation"); + if (current_name == p_filter) { + undo_redo->add_do_method(E, "set_animation", p_new_animation); + } + String autoplay_name = E->call("get_autoplay"); + if (autoplay_name == p_filter) { + undo_redo->add_do_method(E, "set_autoplay", p_new_autoplay); + } + } + } } void SpriteFramesEditor::_animation_add() { - String name = "New Anim"; + String name = "new_animation"; int counter = 0; while (frames->has_animation(name)) { counter++; - name = "New Anim " + itos(counter); + name = vformat("new_animation_%d", counter); } - List<Node *> nodes; - _find_anim_sprites(EditorNode::get_singleton()->get_edited_scene(), &nodes, Ref<SpriteFrames>(frames)); - - undo_redo->create_action(TTR("Add Animation")); - undo_redo->add_do_method(frames, "add_animation", name); - undo_redo->add_undo_method(frames, "remove_animation", name); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Add Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "add_animation", name); + undo_redo->add_undo_method(frames.ptr(), "remove_animation", name); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); - - for (Node *E : nodes) { - String current = E->call("get_animation"); - undo_redo->add_do_method(E, "set_animation", name); - undo_redo->add_undo_method(E, "set_animation", current); - } - - edited_anim = name; - undo_redo->commit_action(); + + _select_animation(name); animations->grab_focus(); } @@ -697,22 +894,43 @@ void SpriteFramesEditor::_animation_remove() { } void SpriteFramesEditor::_animation_remove_confirmed() { - undo_redo->create_action(TTR("Remove Animation")); - undo_redo->add_do_method(frames, "remove_animation", edited_anim); - undo_redo->add_undo_method(frames, "add_animation", edited_anim); - undo_redo->add_undo_method(frames, "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim)); - undo_redo->add_undo_method(frames, "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim)); + StringName new_edited; + List<StringName> anim_names; + frames->get_animation_list(&anim_names); + anim_names.sort_custom<StringName::AlphCompare>(); + if (anim_names.size() >= 2) { + if (edited_anim == anim_names[0]) { + new_edited = anim_names[1]; + } else { + new_edited = anim_names[0]; + } + } else { + new_edited = StringName(); + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Remove Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + _rename_node_animation(undo_redo, false, edited_anim, new_edited, ""); + undo_redo->add_do_method(frames.ptr(), "remove_animation", edited_anim); + undo_redo->add_undo_method(frames.ptr(), "add_animation", edited_anim); + _rename_node_animation(undo_redo, true, edited_anim, edited_anim, edited_anim); + undo_redo->add_undo_method(frames.ptr(), "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim)); + undo_redo->add_undo_method(frames.ptr(), "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim)); int fc = frames->get_frame_count(edited_anim); for (int i = 0; i < fc; i++) { - Ref<Texture2D> frame = frames->get_frame(edited_anim, i); - undo_redo->add_undo_method(frames, "add_frame", edited_anim, frame); + Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, i); + float duration = frames->get_frame_duration(edited_anim, i); + undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration); } undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); + undo_redo->commit_action(); - edited_anim = StringName(); + _select_animation(new_edited); +} - undo_redo->commit_action(); +void SpriteFramesEditor::_animation_search_text_changed(const String &p_text) { + _update_library(); } void SpriteFramesEditor::_animation_loop_changed() { @@ -720,29 +938,30 @@ void SpriteFramesEditor::_animation_loop_changed() { return; } - undo_redo->create_action(TTR("Change Animation Loop")); - undo_redo->add_do_method(frames, "set_animation_loop", edited_anim, anim_loop->is_pressed()); - undo_redo->add_undo_method(frames, "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim)); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Change Animation Loop"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "set_animation_loop", edited_anim, anim_loop->is_pressed()); + undo_redo->add_undo_method(frames.ptr(), "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim)); undo_redo->add_do_method(this, "_update_library", true); undo_redo->add_undo_method(this, "_update_library", true); undo_redo->commit_action(); } -void SpriteFramesEditor::_animation_fps_changed(double p_value) { +void SpriteFramesEditor::_animation_speed_changed(double p_value) { if (updating) { return; } - undo_redo->create_action(TTR("Change Animation FPS"), UndoRedo::MERGE_ENDS); - undo_redo->add_do_method(frames, "set_animation_speed", edited_anim, p_value); - undo_redo->add_undo_method(frames, "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim)); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Change Animation FPS"), UndoRedo::MERGE_ENDS, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "set_animation_speed", edited_anim, p_value); + undo_redo->add_undo_method(frames.ptr(), "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim)); undo_redo->add_do_method(this, "_update_library", true); undo_redo->add_undo_method(this, "_update_library", true); - undo_redo->commit_action(); } -void SpriteFramesEditor::_tree_input(const Ref<InputEvent> &p_event) { +void SpriteFramesEditor::_frame_list_gui_input(const Ref<InputEvent> &p_event) { const Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { @@ -758,6 +977,42 @@ void SpriteFramesEditor::_tree_input(const Ref<InputEvent> &p_event) { } } +void SpriteFramesEditor::_frame_list_item_selected(int p_index) { + if (updating) { + return; + } + + sel = p_index; + + updating = true; + frame_duration->set_value(frames->get_frame_duration(edited_anim, p_index)); + updating = false; +} + +void SpriteFramesEditor::_frame_duration_changed(double p_value) { + if (updating) { + return; + } + + int index = frame_list->get_current(); + if (index < 0) { + return; + } + + sel = index; + + Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, index); + float old_duration = frames->get_frame_duration(edited_anim, index); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Set Frame Duration"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, index, texture, p_value); + undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, index, texture, old_duration); + undo_redo->add_do_method(this, "_update_library"); + undo_redo->add_undo_method(this, "_update_library"); + undo_redo->commit_action(); +} + void SpriteFramesEditor::_zoom_in() { // Do not zoom in or out with no visible frames if (frames->get_frame_count(edited_anim) <= 0) { @@ -766,8 +1021,8 @@ void SpriteFramesEditor::_zoom_in() { if (thumbnail_zoom < max_thumbnail_zoom) { thumbnail_zoom *= scale_ratio; int thumbnail_size = (int)(thumbnail_default_size * thumbnail_zoom); - tree->set_fixed_column_width(thumbnail_size * 3 / 2); - tree->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size)); + frame_list->set_fixed_column_width(thumbnail_size * 3 / 2); + frame_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size)); } } @@ -779,34 +1034,45 @@ void SpriteFramesEditor::_zoom_out() { if (thumbnail_zoom > min_thumbnail_zoom) { thumbnail_zoom /= scale_ratio; int thumbnail_size = (int)(thumbnail_default_size * thumbnail_zoom); - tree->set_fixed_column_width(thumbnail_size * 3 / 2); - tree->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size)); + frame_list->set_fixed_column_width(thumbnail_size * 3 / 2); + frame_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size)); } } void SpriteFramesEditor::_zoom_reset() { thumbnail_zoom = MAX(1.0f, EDSCALE); - tree->set_fixed_column_width(thumbnail_default_size * 3 / 2); - tree->set_fixed_icon_size(Size2(thumbnail_default_size, thumbnail_default_size)); + frame_list->set_fixed_column_width(thumbnail_default_size * 3 / 2); + frame_list->set_fixed_icon_size(Size2(thumbnail_default_size, thumbnail_default_size)); } void SpriteFramesEditor::_update_library(bool p_skip_selector) { + if (frames.is_null()) { + return; + } + updating = true; + frame_duration->set_value(1.0); // Default. + if (!p_skip_selector) { animations->clear(); TreeItem *anim_root = animations->create_item(); List<StringName> anim_names; - frames->get_animation_list(&anim_names); - anim_names.sort_custom<StringName::AlphCompare>(); + bool searching = anim_search_box->get_text().size(); + String searched_string = searching ? anim_search_box->get_text().to_lower() : String(); + for (const StringName &E : anim_names) { String name = E; + if (searching && name.to_lower().find(searched_string) < 0) { + continue; + } + TreeItem *it = animations->create_item(anim_root); it->set_metadata(0, name); @@ -814,13 +1080,28 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) { it->set_text(0, name); it->set_editable(0, true); + if (animated_sprite) { + if (name == String(animated_sprite->call("get_autoplay"))) { + it->set_icon(0, autoplay_icon); + } + } + if (E == edited_anim) { it->select(0); } } } - tree->clear(); + if (animated_sprite) { + String autoplay_name = animated_sprite->call("get_autoplay"); + if (autoplay_name.is_empty()) { + autoplay->set_pressed(false); + } else { + autoplay->set_pressed(String(edited_anim) == autoplay_name); + } + } + + frame_list->clear(); if (!frames->has_animation(edited_anim)) { updating = false; @@ -834,23 +1115,41 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) { } for (int i = 0; i < frames->get_frame_count(edited_anim); i++) { - String name; - Ref<Texture2D> icon; - - if (frames->get_frame(edited_anim, i).is_null()) { - name = itos(i) + ": " + TTR("(empty)"); + String name = itos(i); + Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, i); + float duration = frames->get_frame_duration(edited_anim, i); + + if (texture.is_null()) { + texture = empty_icon; + name += ": " + TTR("(empty)"); + } else if (!texture->get_name().is_empty()) { + name += ": " + texture->get_name(); + } - } else { - name = itos(i) + ": " + frames->get_frame(edited_anim, i)->get_name(); - icon = frames->get_frame(edited_anim, i); + if (duration != 1.0f) { + name += String::utf8(" [× ") + String::num(duration, 2) + "]"; } - tree->add_item(name, icon); - if (frames->get_frame(edited_anim, i).is_valid()) { - tree->set_item_tooltip(tree->get_item_count() - 1, frames->get_frame(edited_anim, i)->get_path()); + frame_list->add_item(name, texture); + if (texture.is_valid()) { + String tooltip = texture->get_path(); + + // Frame is often saved as an AtlasTexture subresource within a scene/resource file, + // thus its path might be not what the user is looking for. So we're also showing + // subsequent source texture paths. + String prefix = String::utf8("┖╴"); + Ref<AtlasTexture> at = texture; + while (at.is_valid() && at->get_atlas().is_valid()) { + tooltip += "\n" + prefix + at->get_atlas()->get_path(); + prefix = " " + prefix; + at = at->get_atlas(); + } + + frame_list->set_item_tooltip(-1, tooltip); } if (sel == i) { - tree->select(tree->get_item_count() - 1); + frame_list->select(frame_list->get_item_count() - 1); + frame_duration->set_value(frames->get_frame_duration(edited_anim, i)); } } @@ -858,49 +1157,76 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) { anim_loop->set_pressed(frames->get_animation_loop(edited_anim)); updating = false; - //player->add_resource("default",resource); } -void SpriteFramesEditor::edit(SpriteFrames *p_frames) { - if (frames == p_frames) { +void SpriteFramesEditor::_edit() { + if (!animated_sprite) { + return; + } + edit(animated_sprite->call("get_sprite_frames")); +} + +void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) { + _update_stop_icon(); + + if (!p_frames.is_valid()) { + frames.unref(); + hide(); return; } frames = p_frames; + read_only = EditorNode::get_singleton()->is_resource_read_only(p_frames); - if (p_frames) { - if (!p_frames->has_animation(edited_anim)) { - List<StringName> anim_names; - frames->get_animation_list(&anim_names); - anim_names.sort_custom<StringName::AlphCompare>(); - if (anim_names.size()) { - edited_anim = anim_names.front()->get(); - } else { - edited_anim = StringName(); - } + if (!p_frames->has_animation(edited_anim)) { + List<StringName> anim_names; + frames->get_animation_list(&anim_names); + anim_names.sort_custom<StringName::AlphCompare>(); + if (anim_names.size()) { + edited_anim = anim_names.front()->get(); + } else { + edited_anim = StringName(); } - - _update_library(); - // Clear zoom and split sheet texture - split_sheet_preview->set_texture(Ref<Texture2D>()); - _zoom_reset(); - } else { - hide(); } + + _update_library(); + // Clear zoom and split sheet texture + split_sheet_preview->set_texture(Ref<Texture2D>()); + _zoom_reset(); + + add_anim->set_disabled(read_only); + delete_anim->set_disabled(read_only); + anim_speed->set_editable(!read_only); + anim_loop->set_disabled(read_only); + load->set_disabled(read_only); + load_sheet->set_disabled(read_only); + copy->set_disabled(read_only); + paste->set_disabled(read_only); + empty_before->set_disabled(read_only); + empty_after->set_disabled(read_only); + move_up->set_disabled(read_only); + move_down->set_disabled(read_only); + delete_frame->set_disabled(read_only); + + _fetch_sprite_node(); // Fetch node after set frames. } Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { + if (read_only) { + return false; + } + if (!frames->has_animation(edited_anim)) { return false; } - int idx = tree->get_item_at_position(p_point, true); + int idx = frame_list->get_item_at_position(p_point, true); if (idx < 0 || idx >= frames->get_frame_count(edited_anim)) { return Variant(); } - RES frame = frames->get_frame(edited_anim, idx); + Ref<Resource> frame = frames->get_frame_texture(edited_anim, idx); if (frame.is_null()) { return Variant(); @@ -912,6 +1238,10 @@ Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_f } bool SpriteFramesEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + if (read_only) { + return false; + } + Dictionary d = p_data; if (!d.has("type")) { @@ -919,12 +1249,12 @@ bool SpriteFramesEditor::can_drop_data_fw(const Point2 &p_point, const Variant & } // reordering frames - if (d.has("from") && (Object *)(d["from"]) == tree) { + if (d.has("from") && (Object *)(d["from"]) == frame_list) { return true; } if (String(d["type"]) == "resource" && d.has("resource")) { - RES r = d["resource"]; + Ref<Resource> r = d["resource"]; Ref<Texture2D> texture = r; @@ -941,8 +1271,8 @@ bool SpriteFramesEditor::can_drop_data_fw(const Point2 &p_point, const Variant & } for (int i = 0; i < files.size(); i++) { - String file = files[i]; - String ftype = EditorFileSystem::get_singleton()->get_file_type(file); + String f = files[i]; + String ftype = EditorFileSystem::get_singleton()->get_file_type(f); if (!ClassDB::is_parent_class(ftype, "Texture2D")) { return false; @@ -965,37 +1295,40 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da return; } - int at_pos = tree->get_item_at_position(p_point, true); + int at_pos = frame_list->get_item_at_position(p_point, true); if (String(d["type"]) == "resource" && d.has("resource")) { - RES r = d["resource"]; + Ref<Resource> r = d["resource"]; Ref<Texture2D> texture = r; if (texture.is_valid()) { bool reorder = false; - if (d.has("from") && (Object *)(d["from"]) == tree) { + if (d.has("from") && (Object *)(d["from"]) == frame_list) { reorder = true; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (reorder) { //drop is from reordering frames int from_frame = -1; + float duration = 1.0; if (d.has("frame")) { from_frame = d["frame"]; + duration = frames->get_frame_duration(edited_anim, from_frame); } - undo_redo->create_action(TTR("Move Frame")); - undo_redo->add_do_method(frames, "remove_frame", edited_anim, from_frame == -1 ? frames->get_frame_count(edited_anim) : from_frame); - undo_redo->add_do_method(frames, "add_frame", edited_anim, texture, at_pos == -1 ? -1 : at_pos); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) - 1 : at_pos); - undo_redo->add_undo_method(frames, "add_frame", edited_anim, texture, from_frame); + undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "remove_frame", edited_anim, from_frame == -1 ? frames->get_frame_count(edited_anim) : from_frame); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, duration, at_pos == -1 ? -1 : at_pos); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) - 1 : at_pos); + undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration, from_frame); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); } else { - undo_redo->create_action(TTR("Add Frame")); - undo_redo->add_do_method(frames, "add_frame", edited_anim, texture, at_pos == -1 ? -1 : at_pos); - undo_redo->add_undo_method(frames, "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) : at_pos); + undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, at_pos == -1 ? -1 : at_pos); + undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) : at_pos); undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); undo_redo->commit_action(); @@ -1014,11 +1347,154 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } } +void SpriteFramesEditor::_update_stop_icon() { + bool is_playing = false; + if (animated_sprite) { + is_playing = animated_sprite->call("is_playing"); + } + if (is_playing) { + stop->set_icon(pause_icon); + } else { + stop->set_icon(stop_icon); + } +} + +void SpriteFramesEditor::_remove_sprite_node() { + if (!animated_sprite) { + return; + } + if (animated_sprite->is_connected("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit))) { + animated_sprite->disconnect("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit)); + } + if (animated_sprite->is_connected("animation_changed", callable_mp(this, &SpriteFramesEditor::_sync_animation))) { + animated_sprite->disconnect("animation_changed", callable_mp(this, &SpriteFramesEditor::_sync_animation)); + } + if (animated_sprite->is_connected("animation_finished", callable_mp(this, &SpriteFramesEditor::_update_stop_icon))) { + animated_sprite->disconnect("animation_finished", callable_mp(this, &SpriteFramesEditor::_update_stop_icon)); + } + animated_sprite = nullptr; +} + +void SpriteFramesEditor::_fetch_sprite_node() { + Node *selected = nullptr; + EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); + if (editor_selection->get_selected_node_list().size() == 1) { + selected = editor_selection->get_selected_node_list()[0]; + } + + bool show_node_edit = false; + AnimatedSprite2D *as2d = Object::cast_to<AnimatedSprite2D>(selected); + AnimatedSprite3D *as3d = Object::cast_to<AnimatedSprite3D>(selected); + if (as2d || as3d) { + if (frames != selected->call("get_sprite_frames")) { + _remove_sprite_node(); + } else { + animated_sprite = selected; + if (!animated_sprite->is_connected("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit))) { + animated_sprite->connect("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit)); + } + if (!animated_sprite->is_connected("animation_changed", callable_mp(this, &SpriteFramesEditor::_sync_animation))) { + animated_sprite->connect("animation_changed", callable_mp(this, &SpriteFramesEditor::_sync_animation), CONNECT_DEFERRED); + } + if (!animated_sprite->is_connected("animation_finished", callable_mp(this, &SpriteFramesEditor::_update_stop_icon))) { + animated_sprite->connect("animation_finished", callable_mp(this, &SpriteFramesEditor::_update_stop_icon)); + } + show_node_edit = true; + } + } else { + _remove_sprite_node(); + } + + if (show_node_edit) { + _sync_animation(); + autoplay_container->show(); + playback_container->show(); + } else { + _update_library(); // To init autoplay icon. + autoplay_container->hide(); + playback_container->hide(); + } +} + +void SpriteFramesEditor::_play_pressed() { + if (animated_sprite) { + animated_sprite->call("stop"); + animated_sprite->call("play", animated_sprite->call("get_animation")); + } + _update_stop_icon(); +} + +void SpriteFramesEditor::_play_from_pressed() { + if (animated_sprite) { + animated_sprite->call("play", animated_sprite->call("get_animation")); + } + _update_stop_icon(); +} + +void SpriteFramesEditor::_play_bw_pressed() { + if (animated_sprite) { + animated_sprite->call("stop"); + animated_sprite->call("play_backwards", animated_sprite->call("get_animation")); + } + _update_stop_icon(); +} + +void SpriteFramesEditor::_play_bw_from_pressed() { + if (animated_sprite) { + animated_sprite->call("play_backwards", animated_sprite->call("get_animation")); + } + _update_stop_icon(); +} + +void SpriteFramesEditor::_stop_pressed() { + if (animated_sprite) { + if (animated_sprite->call("is_playing")) { + animated_sprite->call("pause"); + } else { + animated_sprite->call("stop"); + } + } + _update_stop_icon(); +} + +void SpriteFramesEditor::_autoplay_pressed() { + if (updating) { + return; + } + + if (animated_sprite) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Toggle Autoplay"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + String current = animated_sprite->call("get_animation"); + String current_auto = animated_sprite->call("get_autoplay"); + if (current == current_auto) { + //unset + undo_redo->add_do_method(animated_sprite, "set_autoplay", ""); + undo_redo->add_undo_method(animated_sprite, "set_autoplay", current_auto); + } else { + //set + undo_redo->add_do_method(animated_sprite, "set_autoplay", current); + undo_redo->add_undo_method(animated_sprite, "set_autoplay", current_auto); + } + undo_redo->add_do_method(this, "_update_library"); + undo_redo->add_undo_method(this, "_update_library"); + undo_redo->commit_action(); + } + + _update_library(); +} + void SpriteFramesEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_library", "skipsel"), &SpriteFramesEditor::_update_library, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &SpriteFramesEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &SpriteFramesEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("_drop_data_fw"), &SpriteFramesEditor::drop_data_fw); +} + +void SpriteFramesEditor::_node_removed(Node *p_node) { + if (animated_sprite) { + if (animated_sprite != p_node) { + return; + } + _remove_sprite_node(); + } } SpriteFramesEditor::SpriteFramesEditor() { @@ -1033,42 +1509,65 @@ SpriteFramesEditor::SpriteFramesEditor() { HBoxContainer *hbc_animlist = memnew(HBoxContainer); sub_vb->add_child(hbc_animlist); - new_anim = memnew(Button); - new_anim->set_flat(true); - new_anim->set_tooltip(TTR("New Animation")); - hbc_animlist->add_child(new_anim); - new_anim->connect("pressed", callable_mp(this, &SpriteFramesEditor::_animation_add)); + add_anim = memnew(Button); + add_anim->set_flat(true); + hbc_animlist->add_child(add_anim); + add_anim->connect("pressed", callable_mp(this, &SpriteFramesEditor::_animation_add)); + + delete_anim = memnew(Button); + delete_anim->set_flat(true); + hbc_animlist->add_child(delete_anim); + delete_anim->set_disabled(true); + delete_anim->connect("pressed", callable_mp(this, &SpriteFramesEditor::_animation_remove)); + + autoplay_container = memnew(HBoxContainer); + hbc_animlist->add_child(autoplay_container); + + autoplay_container->add_child(memnew(VSeparator)); + + autoplay = memnew(Button); + autoplay->set_flat(true); + autoplay->set_tooltip_text(TTR("Autoplay on Load")); + autoplay_container->add_child(autoplay); + + hbc_animlist->add_child(memnew(VSeparator)); + + anim_loop = memnew(Button); + anim_loop->set_toggle_mode(true); + anim_loop->set_flat(true); + anim_loop->set_tooltip_text(TTR("Animation Looping")); + anim_loop->connect("pressed", callable_mp(this, &SpriteFramesEditor::_animation_loop_changed)); + hbc_animlist->add_child(anim_loop); - remove_anim = memnew(Button); - remove_anim->set_flat(true); - remove_anim->set_tooltip(TTR("Remove Animation")); - hbc_animlist->add_child(remove_anim); - remove_anim->connect("pressed", callable_mp(this, &SpriteFramesEditor::_animation_remove)); + anim_speed = memnew(SpinBox); + anim_speed->set_suffix(TTR("FPS")); + anim_speed->set_min(0); + anim_speed->set_max(120); + anim_speed->set_step(0.01); + anim_speed->set_custom_arrow_step(1); + anim_speed->set_tooltip_text(TTR("Animation Speed")); + anim_speed->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_animation_speed_changed)); + hbc_animlist->add_child(anim_speed); + + anim_search_box = memnew(LineEdit); + sub_vb->add_child(anim_search_box); + anim_search_box->set_h_size_flags(SIZE_EXPAND_FILL); + anim_search_box->set_placeholder(TTR("Filter Animations")); + anim_search_box->set_clear_button_enabled(true); + anim_search_box->connect("text_changed", callable_mp(this, &SpriteFramesEditor::_animation_search_text_changed)); animations = memnew(Tree); sub_vb->add_child(animations); animations->set_v_size_flags(SIZE_EXPAND_FILL); animations->set_hide_root(true); - animations->connect("cell_selected", callable_mp(this, &SpriteFramesEditor::_animation_select)); + animations->connect("cell_selected", callable_mp(this, &SpriteFramesEditor::_animation_selected)); animations->connect("item_edited", callable_mp(this, &SpriteFramesEditor::_animation_name_edited)); animations->set_allow_reselect(true); - HBoxContainer *hbc_anim_speed = memnew(HBoxContainer); - hbc_anim_speed->add_child(memnew(Label(TTR("Speed:")))); - vbc_animlist->add_child(hbc_anim_speed); - anim_speed = memnew(SpinBox); - anim_speed->set_suffix(TTR("FPS")); - anim_speed->set_min(0); - anim_speed->set_max(100); - anim_speed->set_step(0.01); - anim_speed->set_h_size_flags(SIZE_EXPAND_FILL); - hbc_anim_speed->add_child(anim_speed); - anim_speed->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_animation_fps_changed)); - - anim_loop = memnew(CheckButton); - anim_loop->set_text(TTR("Loop")); - vbc_animlist->add_child(anim_loop); - anim_loop->connect("pressed", callable_mp(this, &SpriteFramesEditor::_animation_loop_changed)); + add_anim->set_shortcut_context(animations); + add_anim->set_shortcut(ED_SHORTCUT("sprite_frames/new_animation", TTR("Add Animation"), KeyModifierMask::CMD_OR_CTRL | Key::N)); + delete_anim->set_shortcut_context(animations); + delete_anim->set_shortcut(ED_SHORTCUT("sprite_frames/delete_animation", TTR("Delete Animation"), Key::KEY_DELETE)); VBoxContainer *vbc = memnew(VBoxContainer); add_child(vbc); @@ -1080,105 +1579,179 @@ SpriteFramesEditor::SpriteFramesEditor() { HBoxContainer *hbc = memnew(HBoxContainer); sub_vb->add_child(hbc); + playback_container = memnew(HBoxContainer); + hbc->add_child(playback_container); + + play_bw_from = memnew(Button); + play_bw_from->set_flat(true); + play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)")); + playback_container->add_child(play_bw_from); + + play_bw = memnew(Button); + play_bw->set_flat(true); + play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)")); + playback_container->add_child(play_bw); + + stop = memnew(Button); + stop->set_flat(true); + stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)")); + playback_container->add_child(stop); + + play = memnew(Button); + play->set_flat(true); + play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)")); + playback_container->add_child(play); + + play_from = memnew(Button); + play_from->set_flat(true); + play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)")); + playback_container->add_child(play_from); + + playback_container->add_child(memnew(VSeparator)); + + autoplay->connect("pressed", callable_mp(this, &SpriteFramesEditor::_autoplay_pressed)); + autoplay->set_toggle_mode(true); + play->connect("pressed", callable_mp(this, &SpriteFramesEditor::_play_pressed)); + play_from->connect("pressed", callable_mp(this, &SpriteFramesEditor::_play_from_pressed)); + play_bw->connect("pressed", callable_mp(this, &SpriteFramesEditor::_play_bw_pressed)); + play_bw_from->connect("pressed", callable_mp(this, &SpriteFramesEditor::_play_bw_from_pressed)); + stop->connect("pressed", callable_mp(this, &SpriteFramesEditor::_stop_pressed)); + load = memnew(Button); load->set_flat(true); - load->set_tooltip(TTR("Add a Texture from File")); hbc->add_child(load); load_sheet = memnew(Button); load_sheet->set_flat(true); - load_sheet->set_tooltip(TTR("Add Frames from a Sprite Sheet")); hbc->add_child(load_sheet); hbc->add_child(memnew(VSeparator)); copy = memnew(Button); copy->set_flat(true); - copy->set_tooltip(TTR("Copy")); hbc->add_child(copy); paste = memnew(Button); paste->set_flat(true); - paste->set_tooltip(TTR("Paste")); hbc->add_child(paste); hbc->add_child(memnew(VSeparator)); - empty = memnew(Button); - empty->set_flat(true); - empty->set_tooltip(TTR("Insert Empty (Before)")); - hbc->add_child(empty); + empty_before = memnew(Button); + empty_before->set_flat(true); + hbc->add_child(empty_before); - empty2 = memnew(Button); - empty2->set_flat(true); - empty2->set_tooltip(TTR("Insert Empty (After)")); - hbc->add_child(empty2); + empty_after = memnew(Button); + empty_after->set_flat(true); + hbc->add_child(empty_after); hbc->add_child(memnew(VSeparator)); move_up = memnew(Button); move_up->set_flat(true); - move_up->set_tooltip(TTR("Move (Before)")); hbc->add_child(move_up); move_down = memnew(Button); move_down->set_flat(true); - move_down->set_tooltip(TTR("Move (After)")); hbc->add_child(move_down); - _delete = memnew(Button); - _delete->set_flat(true); - _delete->set_tooltip(TTR("Delete")); - hbc->add_child(_delete); + delete_frame = memnew(Button); + delete_frame->set_flat(true); + hbc->add_child(delete_frame); + + hbc->add_child(memnew(VSeparator)); + + Label *label = memnew(Label); + label->set_text(TTR("Frame Duration:")); + hbc->add_child(label); + + frame_duration = memnew(SpinBox); + frame_duration->set_prefix(String::utf8("×")); + frame_duration->set_min(SPRITE_FRAME_MINIMUM_DURATION); // Avoid zero div. + frame_duration->set_max(10); + frame_duration->set_step(0.01); + frame_duration->set_custom_arrow_step(0.1); + frame_duration->set_allow_lesser(false); + frame_duration->set_allow_greater(true); + hbc->add_child(frame_duration); hbc->add_spacer(); zoom_out = memnew(Button); zoom_out->connect("pressed", callable_mp(this, &SpriteFramesEditor::_zoom_out)); zoom_out->set_flat(true); - zoom_out->set_tooltip(TTR("Zoom Out")); + zoom_out->set_tooltip_text(TTR("Zoom Out")); hbc->add_child(zoom_out); zoom_reset = memnew(Button); zoom_reset->connect("pressed", callable_mp(this, &SpriteFramesEditor::_zoom_reset)); zoom_reset->set_flat(true); - zoom_reset->set_tooltip(TTR("Zoom Reset")); + zoom_reset->set_tooltip_text(TTR("Zoom Reset")); hbc->add_child(zoom_reset); zoom_in = memnew(Button); zoom_in->connect("pressed", callable_mp(this, &SpriteFramesEditor::_zoom_in)); zoom_in->set_flat(true); - zoom_in->set_tooltip(TTR("Zoom In")); + zoom_in->set_tooltip_text(TTR("Zoom In")); hbc->add_child(zoom_in); file = memnew(EditorFileDialog); add_child(file); - tree = memnew(ItemList); - tree->set_v_size_flags(SIZE_EXPAND_FILL); - tree->set_icon_mode(ItemList::ICON_MODE_TOP); + frame_list = memnew(ItemList); + frame_list->set_v_size_flags(SIZE_EXPAND_FILL); + frame_list->set_icon_mode(ItemList::ICON_MODE_TOP); - tree->set_max_columns(0); - tree->set_icon_mode(ItemList::ICON_MODE_TOP); - tree->set_max_text_lines(2); - tree->set_drag_forwarding(this); - tree->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_tree_input)); + frame_list->set_max_columns(0); + frame_list->set_icon_mode(ItemList::ICON_MODE_TOP); + frame_list->set_max_text_lines(2); + SET_DRAG_FORWARDING_GCD(frame_list, SpriteFramesEditor); + frame_list->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_frame_list_gui_input)); + frame_list->connect("item_selected", callable_mp(this, &SpriteFramesEditor::_frame_list_item_selected)); - sub_vb->add_child(tree); + sub_vb->add_child(frame_list); dialog = memnew(AcceptDialog); add_child(dialog); load->connect("pressed", callable_mp(this, &SpriteFramesEditor::_load_pressed)); load_sheet->connect("pressed", callable_mp(this, &SpriteFramesEditor::_open_sprite_sheet)); - _delete->connect("pressed", callable_mp(this, &SpriteFramesEditor::_delete_pressed)); + delete_frame->connect("pressed", callable_mp(this, &SpriteFramesEditor::_delete_pressed)); copy->connect("pressed", callable_mp(this, &SpriteFramesEditor::_copy_pressed)); paste->connect("pressed", callable_mp(this, &SpriteFramesEditor::_paste_pressed)); - empty->connect("pressed", callable_mp(this, &SpriteFramesEditor::_empty_pressed)); - empty2->connect("pressed", callable_mp(this, &SpriteFramesEditor::_empty2_pressed)); + empty_before->connect("pressed", callable_mp(this, &SpriteFramesEditor::_empty_pressed)); + empty_after->connect("pressed", callable_mp(this, &SpriteFramesEditor::_empty2_pressed)); move_up->connect("pressed", callable_mp(this, &SpriteFramesEditor::_up_pressed)); move_down->connect("pressed", callable_mp(this, &SpriteFramesEditor::_down_pressed)); - file->connect("files_selected", callable_mp(this, &SpriteFramesEditor::_file_load_request), make_binds(-1)); + + load->set_shortcut_context(frame_list); + load->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_file", TTR("Add frame from file"), KeyModifierMask::CMD_OR_CTRL | Key::O)); + load_sheet->set_shortcut_context(frame_list); + load_sheet->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_sheet", TTR("Add frames from sprite sheet"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::O)); + delete_frame->set_shortcut_context(frame_list); + delete_frame->set_shortcut(ED_SHORTCUT("sprite_frames/delete", TTR("Delete Frame"), Key::KEY_DELETE)); + copy->set_shortcut_context(frame_list); + copy->set_shortcut(ED_SHORTCUT("sprite_frames/copy", TTR("Copy Frame"), KeyModifierMask::CMD_OR_CTRL | Key::C)); + paste->set_shortcut_context(frame_list); + paste->set_shortcut(ED_SHORTCUT("sprite_frames/paste", TTR("Paste Frame"), KeyModifierMask::CMD_OR_CTRL | Key::V)); + empty_before->set_shortcut_context(frame_list); + empty_before->set_shortcut(ED_SHORTCUT("sprite_frames/empty_before", TTR("Insert Empty (Before Selected)"), KeyModifierMask::ALT | Key::LEFT)); + empty_after->set_shortcut_context(frame_list); + empty_after->set_shortcut(ED_SHORTCUT("sprite_frames/empty_after", TTR("Insert Empty (After Selected)"), KeyModifierMask::ALT | Key::RIGHT)); + move_up->set_shortcut_context(frame_list); + move_up->set_shortcut(ED_SHORTCUT("sprite_frames/move_left", TTR("Move Frame Left"), KeyModifierMask::CMD_OR_CTRL | Key::LEFT)); + move_down->set_shortcut_context(frame_list); + move_down->set_shortcut(ED_SHORTCUT("sprite_frames/move_right", TTR("Move Frame Right"), KeyModifierMask::CMD_OR_CTRL | Key::RIGHT)); + + zoom_out->set_shortcut_context(frame_list); + zoom_out->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_out", TTR("Zoom Out"), + { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::MINUS), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_SUBTRACT) })); + zoom_in->set_shortcut_context(frame_list); + zoom_in->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_in", TTR("Zoom In"), + { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::EQUAL), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_ADD) })); + + file->connect("files_selected", callable_mp(this, &SpriteFramesEditor::_file_load_request).bind(-1)); + frame_duration->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_frame_duration_changed)); loading_scene = false; sel = -1; @@ -1199,23 +1772,66 @@ SpriteFramesEditor::SpriteFramesEditor() { HBoxContainer *split_sheet_hb = memnew(HBoxContainer); - Label *ss_label = memnew(Label(TTR("Horizontal:"))); - split_sheet_hb->add_child(ss_label); + split_sheet_hb->add_child(memnew(Label(TTR("Horizontal:")))); split_sheet_h = memnew(SpinBox); split_sheet_h->set_min(1); split_sheet_h->set_max(128); split_sheet_h->set_step(1); split_sheet_hb->add_child(split_sheet_h); - split_sheet_h->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed)); + split_sheet_h->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT)); - ss_label = memnew(Label(TTR("Vertical:"))); - split_sheet_hb->add_child(ss_label); + split_sheet_hb->add_child(memnew(Label(TTR("Vertical:")))); split_sheet_v = memnew(SpinBox); split_sheet_v->set_min(1); split_sheet_v->set_max(128); split_sheet_v->set_step(1); split_sheet_hb->add_child(split_sheet_v); - split_sheet_v->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed)); + split_sheet_v->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT)); + + split_sheet_hb->add_child(memnew(VSeparator)); + split_sheet_hb->add_child(memnew(Label(TTR("Size:")))); + split_sheet_size_x = memnew(SpinBox); + split_sheet_size_x->set_min(1); + split_sheet_size_x->set_step(1); + split_sheet_size_x->set_suffix("px"); + split_sheet_size_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE)); + split_sheet_hb->add_child(split_sheet_size_x); + split_sheet_size_y = memnew(SpinBox); + split_sheet_size_y->set_min(1); + split_sheet_size_y->set_step(1); + split_sheet_size_y->set_suffix("px"); + split_sheet_size_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE)); + split_sheet_hb->add_child(split_sheet_size_y); + + split_sheet_hb->add_child(memnew(VSeparator)); + split_sheet_hb->add_child(memnew(Label(TTR("Separation:")))); + split_sheet_sep_x = memnew(SpinBox); + split_sheet_sep_x->set_min(0); + split_sheet_sep_x->set_step(1); + split_sheet_sep_x->set_suffix("px"); + split_sheet_sep_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT)); + split_sheet_hb->add_child(split_sheet_sep_x); + split_sheet_sep_y = memnew(SpinBox); + split_sheet_sep_y->set_min(0); + split_sheet_sep_y->set_step(1); + split_sheet_sep_y->set_suffix("px"); + split_sheet_sep_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT)); + split_sheet_hb->add_child(split_sheet_sep_y); + + split_sheet_hb->add_child(memnew(VSeparator)); + split_sheet_hb->add_child(memnew(Label(TTR("Offset:")))); + split_sheet_offset_x = memnew(SpinBox); + split_sheet_offset_x->set_min(0); + split_sheet_offset_x->set_step(1); + split_sheet_offset_x->set_suffix("px"); + split_sheet_offset_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT)); + split_sheet_hb->add_child(split_sheet_offset_x); + split_sheet_offset_y = memnew(SpinBox); + split_sheet_offset_y->set_min(0); + split_sheet_offset_y->set_step(1); + split_sheet_offset_y->set_suffix("px"); + split_sheet_offset_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT)); + split_sheet_hb->add_child(split_sheet_offset_y); split_sheet_hb->add_spacer(); @@ -1232,7 +1848,7 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_vb->add_child(split_sheet_panel); split_sheet_preview = memnew(TextureRect); - split_sheet_preview->set_expand(true); + split_sheet_preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); split_sheet_preview->set_mouse_filter(MOUSE_FILTER_PASS); split_sheet_preview->connect("draw", callable_mp(this, &SpriteFramesEditor::_sheet_preview_draw)); split_sheet_preview->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_preview_input)); @@ -1258,21 +1874,21 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_zoom_out = memnew(Button); split_sheet_zoom_out->set_flat(true); split_sheet_zoom_out->set_focus_mode(FOCUS_NONE); - split_sheet_zoom_out->set_tooltip(TTR("Zoom Out")); + split_sheet_zoom_out->set_tooltip_text(TTR("Zoom Out")); split_sheet_zoom_out->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_out)); split_sheet_zoom_hb->add_child(split_sheet_zoom_out); split_sheet_zoom_reset = memnew(Button); split_sheet_zoom_reset->set_flat(true); split_sheet_zoom_reset->set_focus_mode(FOCUS_NONE); - split_sheet_zoom_reset->set_tooltip(TTR("Zoom Reset")); + split_sheet_zoom_reset->set_tooltip_text(TTR("Zoom Reset")); split_sheet_zoom_reset->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_reset)); split_sheet_zoom_hb->add_child(split_sheet_zoom_reset); split_sheet_zoom_in = memnew(Button); split_sheet_zoom_in->set_flat(true); split_sheet_zoom_in->set_focus_mode(FOCUS_NONE); - split_sheet_zoom_in->set_tooltip(TTR("Zoom In")); + split_sheet_zoom_in->set_tooltip_text(TTR("Zoom In")); split_sheet_zoom_in->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_in)); split_sheet_zoom_hb->add_child(split_sheet_zoom_in); @@ -1293,21 +1909,23 @@ SpriteFramesEditor::SpriteFramesEditor() { max_sheet_zoom = 16.0f * MAX(1.0f, EDSCALE); min_sheet_zoom = 0.01f * MAX(1.0f, EDSCALE); _zoom_reset(); + + // Ensure the anim search box is wide enough by default. + // Not by setting its minimum size so it can still be shrunk if desired. + set_split_offset(56 * EDSCALE); } void SpriteFramesEditorPlugin::edit(Object *p_object) { - frames_editor->set_undo_redo(&get_undo_redo()); - - SpriteFrames *s; + Ref<SpriteFrames> s; AnimatedSprite2D *animated_sprite = Object::cast_to<AnimatedSprite2D>(p_object); if (animated_sprite) { - s = *animated_sprite->get_sprite_frames(); + s = animated_sprite->get_sprite_frames(); } else { AnimatedSprite3D *animated_sprite_3d = Object::cast_to<AnimatedSprite3D>(p_object); if (animated_sprite_3d) { - s = *animated_sprite_3d->get_sprite_frames(); + s = animated_sprite_3d->get_sprite_frames(); } else { - s = Object::cast_to<SpriteFrames>(p_object); + s = p_object; } } @@ -1329,20 +1947,17 @@ bool SpriteFramesEditorPlugin::handles(Object *p_object) const { void SpriteFramesEditorPlugin::make_visible(bool p_visible) { if (p_visible) { button->show(); - editor->make_bottom_panel_item_visible(frames_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(frames_editor); } else { button->hide(); - if (frames_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); - } + frames_editor->edit(Ref<SpriteFrames>()); } } -SpriteFramesEditorPlugin::SpriteFramesEditorPlugin(EditorNode *p_node) { - editor = p_node; +SpriteFramesEditorPlugin::SpriteFramesEditorPlugin() { frames_editor = memnew(SpriteFramesEditor); frames_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE); - button = editor->add_bottom_panel_item(TTR("SpriteFrames"), frames_editor); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("SpriteFrames"), frames_editor); button->hide(); } diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index 8767e05a94..1dfb909388 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -1,94 +1,141 @@ -/*************************************************************************/ -/* sprite_frames_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* sprite_frames_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 SPRITE_FRAMES_EDITOR_PLUGIN_H #define SPRITE_FRAMES_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/2d/animated_sprite_2d.h" +#include "scene/3d/sprite_3d.h" +#include "scene/gui/button.h" +#include "scene/gui/check_button.h" #include "scene/gui/dialogs.h" -#include "scene/gui/file_dialog.h" +#include "scene/gui/item_list.h" +#include "scene/gui/line_edit.h" #include "scene/gui/scroll_container.h" +#include "scene/gui/spin_box.h" #include "scene/gui/split_container.h" #include "scene/gui/texture_rect.h" #include "scene/gui/tree.h" +class EditorFileDialog; + +class EditorSpriteFramesFrame : public Resource { + GDCLASS(EditorSpriteFramesFrame, Resource); + +public: + Ref<Texture2D> texture; + float duration; +}; + class SpriteFramesEditor : public HSplitContainer { GDCLASS(SpriteFramesEditor, HSplitContainer); - Button *load; - Button *load_sheet; - Button *_delete; - Button *copy; - Button *paste; - Button *empty; - Button *empty2; - Button *move_up; - Button *move_down; - Button *zoom_out; - Button *zoom_reset; - Button *zoom_in; - ItemList *tree; + Ref<SpriteFrames> frames; + Node *animated_sprite = nullptr; + + enum { + PARAM_USE_CURRENT, // Used in callbacks to indicate `dominant_param` should be not updated. + PARAM_FRAME_COUNT, // Keep "Horizontal" & "Vertical" values. + PARAM_SIZE, // Keep "Size" values. + }; + int dominant_param = PARAM_FRAME_COUNT; + + bool read_only = false; + + Ref<Texture2D> autoplay_icon; + Ref<Texture2D> stop_icon; + Ref<Texture2D> pause_icon; + Ref<Texture2D> empty_icon = memnew(ImageTexture); + + HBoxContainer *playback_container = nullptr; + Button *stop = nullptr; + Button *play = nullptr; + Button *play_from = nullptr; + Button *play_bw = nullptr; + Button *play_bw_from = nullptr; + + Button *load = nullptr; + Button *load_sheet = nullptr; + Button *delete_frame = nullptr; + Button *copy = nullptr; + Button *paste = nullptr; + Button *empty_before = nullptr; + Button *empty_after = nullptr; + Button *move_up = nullptr; + Button *move_down = nullptr; + Button *zoom_out = nullptr; + Button *zoom_reset = nullptr; + Button *zoom_in = nullptr; + SpinBox *frame_duration = nullptr; + ItemList *frame_list = nullptr; bool loading_scene; int sel; - Button *new_anim; - Button *remove_anim; + Button *add_anim = nullptr; + Button *delete_anim = nullptr; + SpinBox *anim_speed = nullptr; + Button *anim_loop = nullptr; - Tree *animations; - SpinBox *anim_speed; - CheckButton *anim_loop; + HBoxContainer *autoplay_container = nullptr; + Button *autoplay = nullptr; - EditorFileDialog *file; + LineEdit *anim_search_box = nullptr; + Tree *animations = nullptr; - AcceptDialog *dialog; + EditorFileDialog *file = nullptr; - SpriteFrames *frames; + AcceptDialog *dialog = nullptr; StringName edited_anim; - ConfirmationDialog *delete_dialog; - - ConfirmationDialog *split_sheet_dialog; - ScrollContainer *split_sheet_scroll; - TextureRect *split_sheet_preview; - SpinBox *split_sheet_h; - SpinBox *split_sheet_v; - Button *split_sheet_zoom_out; - Button *split_sheet_zoom_reset; - Button *split_sheet_zoom_in; - EditorFileDialog *file_split_sheet; - Set<int> frames_selected; - Set<int> frames_toggled_by_mouse_hover; - int last_frame_selected; + ConfirmationDialog *delete_dialog = nullptr; + + ConfirmationDialog *split_sheet_dialog = nullptr; + ScrollContainer *split_sheet_scroll = nullptr; + TextureRect *split_sheet_preview = nullptr; + SpinBox *split_sheet_h = nullptr; + SpinBox *split_sheet_v = nullptr; + SpinBox *split_sheet_size_x = nullptr; + SpinBox *split_sheet_size_y = nullptr; + SpinBox *split_sheet_sep_x = nullptr; + SpinBox *split_sheet_sep_y = nullptr; + SpinBox *split_sheet_offset_x = nullptr; + SpinBox *split_sheet_offset_y = nullptr; + Button *split_sheet_zoom_out = nullptr; + Button *split_sheet_zoom_reset = nullptr; + Button *split_sheet_zoom_in = nullptr; + EditorFileDialog *file_split_sheet = nullptr; + HashSet<int> frames_selected; + HashSet<int> frames_toggled_by_mouse_hover; + int last_frame_selected = 0; float scale_ratio; int thumbnail_default_size; @@ -99,6 +146,11 @@ class SpriteFramesEditor : public HSplitContainer { float max_sheet_zoom; float min_sheet_zoom; + Size2i _get_frame_count() const; + Size2i _get_frame_size() const; + Size2i _get_offset() const; + Size2i _get_separation() const; + void _load_pressed(); void _file_load_request(const Vector<String> &p_path, int p_at_pos = -1); void _copy_pressed(); @@ -108,24 +160,35 @@ class SpriteFramesEditor : public HSplitContainer { void _delete_pressed(); void _up_pressed(); void _down_pressed(); + void _frame_duration_changed(double p_value); void _update_library(bool p_skip_selector = false); - void _animation_select(); + void _update_stop_icon(); + void _play_pressed(); + void _play_from_pressed(); + void _play_bw_pressed(); + void _play_bw_from_pressed(); + void _autoplay_pressed(); + void _stop_pressed(); + + void _animation_selected(); void _animation_name_edited(); void _animation_add(); void _animation_remove(); void _animation_remove_confirmed(); + void _animation_search_text_changed(const String &p_text); void _animation_loop_changed(); - void _animation_fps_changed(double p_value); + void _animation_speed_changed(double p_value); + + void _frame_list_gui_input(const Ref<InputEvent> &p_event); + void _frame_list_item_selected(int p_index); - void _tree_input(const Ref<InputEvent> &p_event); void _zoom_in(); void _zoom_out(); void _zoom_reset(); bool updating; - - UndoRedo *undo_redo; + bool updating_split_settings = false; // Skip SpinBox/Range callback when setting value by code. Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; @@ -135,33 +198,42 @@ class SpriteFramesEditor : public HSplitContainer { void _prepare_sprite_sheet(const String &p_file); int _sheet_preview_position_to_frame_index(const Vector2 &p_position); void _sheet_preview_draw(); - void _sheet_spin_changed(double); + void _sheet_spin_changed(double p_value, int p_dominant_param); void _sheet_preview_input(const Ref<InputEvent> &p_event); void _sheet_scroll_input(const Ref<InputEvent> &p_event); void _sheet_add_frames(); + void _sheet_zoom_on_position(float p_zoom, const Vector2 &p_position); void _sheet_zoom_in(); void _sheet_zoom_out(); void _sheet_zoom_reset(); void _sheet_select_clear_all_frames(); + void _edit(); + void _regist_scene_undo(EditorUndoRedoManager *undo_redo); + void _fetch_sprite_node(); + void _remove_sprite_node(); + + bool sprite_node_updating = false; + void _sync_animation(); + + void _select_animation(const String &p_name, bool p_update_node = true); + void _rename_node_animation(EditorUndoRedoManager *undo_redo, bool is_undo, const String &p_filter, const String &p_new_animation, const String &p_new_autoplay); + protected: void _notification(int p_what); - virtual void gui_input(const Ref<InputEvent> &p_event) override; + void _node_removed(Node *p_node); static void _bind_methods(); public: - void set_undo_redo(UndoRedo *p_undo_redo) { undo_redo = p_undo_redo; } - - void edit(SpriteFrames *p_frames); + void edit(Ref<SpriteFrames> p_frames); SpriteFramesEditor(); }; class SpriteFramesEditorPlugin : public EditorPlugin { GDCLASS(SpriteFramesEditorPlugin, EditorPlugin); - SpriteFramesEditor *frames_editor; - EditorNode *editor; - Button *button; + SpriteFramesEditor *frames_editor = nullptr; + Button *button = nullptr; public: virtual String get_name() const override { return "SpriteFrames"; } @@ -170,7 +242,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - SpriteFramesEditorPlugin(EditorNode *p_node); + SpriteFramesEditorPlugin(); ~SpriteFramesEditorPlugin(); }; diff --git a/editor/plugins/style_box_editor_plugin.cpp b/editor/plugins/style_box_editor_plugin.cpp index 5d38352b22..37fec97c37 100644 --- a/editor/plugins/style_box_editor_plugin.cpp +++ b/editor/plugins/style_box_editor_plugin.cpp @@ -1,36 +1,44 @@ -/*************************************************************************/ -/* style_box_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* style_box_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 "style_box_editor_plugin.h" #include "editor/editor_scale.h" +#include "scene/gui/texture_button.h" + +bool StyleBoxPreview::grid_preview_enabled = true; + +void StyleBoxPreview::_grid_preview_toggled(bool p_active) { + grid_preview_enabled = p_active; + preview->queue_redraw(); +} bool EditorInspectorPluginStyleBox::can_handle(Object *p_object) { return Object::cast_to<StyleBox>(p_object) != nullptr; @@ -53,16 +61,40 @@ void StyleBoxPreview::edit(const Ref<StyleBox> &p_stylebox) { preview->add_theme_style_override("panel", stylebox); stylebox->connect("changed", callable_mp(this, &StyleBoxPreview::_sb_changed)); } + Ref<StyleBoxTexture> sbt = p_stylebox; + grid_preview->set_visible(sbt.is_valid()); _sb_changed(); } void StyleBoxPreview::_sb_changed() { - preview->update(); + preview->queue_redraw(); +} + +void StyleBoxPreview::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + if (!is_inside_tree()) { + // TODO: This is a workaround because `NOTIFICATION_THEME_CHANGED` + // is getting called for some reason when the `TexturePreview` is + // getting destroyed, which causes `get_theme_font()` to return `nullptr`. + // See https://github.com/godotengine/godot/issues/50743. + break; + } + grid_preview->set_texture_normal(get_theme_icon(SNAME("StyleBoxGridInvisible"), SNAME("EditorIcons"))); + grid_preview->set_texture_pressed(get_theme_icon(SNAME("StyleBoxGridVisible"), SNAME("EditorIcons"))); + grid_preview->set_texture_hover(get_theme_icon(SNAME("StyleBoxGridVisible"), SNAME("EditorIcons"))); + checkerboard->set_texture(get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons"))); + } break; + } } void StyleBoxPreview::_redraw() { if (stylebox.is_valid()) { + Ref<Texture2D> grid_texture_disabled = get_theme_icon(SNAME("StyleBoxGridInvisible"), SNAME("EditorIcons")); Rect2 preview_rect = preview->get_rect(); + preview_rect.position += grid_texture_disabled->get_size(); + preview_rect.size -= grid_texture_disabled->get_size() * 2; // Re-adjust preview panel to fit all drawn content Rect2 draw_rect = stylebox->get_draw_rect(preview_rect); @@ -70,6 +102,21 @@ void StyleBoxPreview::_redraw() { preview_rect.position -= draw_rect.position - preview_rect.position; preview->draw_style_box(stylebox, preview_rect); + + Ref<StyleBoxTexture> sbt = stylebox; + if (sbt.is_valid() && grid_preview->is_pressed()) { + for (int i = 0; i < 2; i++) { + Color c = i == 1 ? Color(1, 1, 1, 0.8) : Color(0, 0, 0, 0.4); + int x = draw_rect.position.x + sbt->get_margin(SIDE_LEFT) + (1 - i); + preview->draw_line(Point2(x, 0), Point2(x, preview->get_size().height), c); + int x2 = draw_rect.position.x + draw_rect.size.width - sbt->get_margin(SIDE_RIGHT) + (1 - i); + preview->draw_line(Point2(x2, 0), Point2(x2, preview->get_size().height), c); + int y = draw_rect.position.y + sbt->get_margin(SIDE_TOP) + (1 - i); + preview->draw_line(Point2(0, y), Point2(preview->get_size().width, y), c); + int y2 = draw_rect.position.y + draw_rect.size.height - sbt->get_margin(SIDE_BOTTOM) + (1 - i); + preview->draw_line(Point2(0, y2), Point2(preview->get_size().width, y2), c); + } + } } } @@ -77,14 +124,26 @@ void StyleBoxPreview::_bind_methods() { } StyleBoxPreview::StyleBoxPreview() { + checkerboard = memnew(TextureRect); + checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE); + checkerboard->set_texture_repeat(CanvasItem::TEXTURE_REPEAT_ENABLED); + checkerboard->set_custom_minimum_size(Size2(0.0, 150.0) * EDSCALE); + preview = memnew(Control); - preview->set_custom_minimum_size(Size2(0, 150 * EDSCALE)); preview->set_clip_contents(true); preview->connect("draw", callable_mp(this, &StyleBoxPreview::_redraw)); - add_margin_child(TTR("Preview:"), preview); + checkerboard->add_child(preview); + preview->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + + add_margin_child(TTR("Preview:"), checkerboard); + grid_preview = memnew(TextureButton); + preview->add_child(grid_preview); + grid_preview->set_toggle_mode(true); + grid_preview->connect("toggled", callable_mp(this, &StyleBoxPreview::_grid_preview_toggled)); + grid_preview->set_pressed(grid_preview_enabled); } -StyleBoxEditorPlugin::StyleBoxEditorPlugin(EditorNode *p_node) { +StyleBoxEditorPlugin::StyleBoxEditorPlugin() { Ref<EditorInspectorPluginStyleBox> inspector_plugin; inspector_plugin.instantiate(); add_inspector_plugin(inspector_plugin); diff --git a/editor/plugins/style_box_editor_plugin.h b/editor/plugins/style_box_editor_plugin.h index 898628fd7f..d589bd9fe2 100644 --- a/editor/plugins/style_box_editor_plugin.h +++ b/editor/plugins/style_box_editor_plugin.h @@ -1,50 +1,57 @@ -/*************************************************************************/ -/* style_box_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* style_box_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 STYLE_BOX_EDITOR_PLUGIN_H #define STYLE_BOX_EDITOR_PLUGIN_H #include "editor/editor_inspector.h" -#include "editor/editor_node.h" +#include "editor/editor_plugin.h" #include "scene/gui/option_button.h" #include "scene/gui/texture_rect.h" #include "scene/resources/style_box.h" +class TextureButton; + class StyleBoxPreview : public VBoxContainer { GDCLASS(StyleBoxPreview, VBoxContainer); - Control *preview; + TextureRect *checkerboard = nullptr; + TextureButton *grid_preview = nullptr; + Control *preview = nullptr; Ref<StyleBox> stylebox; void _sb_changed(); void _redraw(); + void _notification(int p_what); + static bool grid_preview_enabled; + void _grid_preview_toggled(bool p_active); protected: static void _bind_methods(); @@ -69,7 +76,7 @@ class StyleBoxEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "StyleBox"; } - StyleBoxEditorPlugin(EditorNode *p_node); + StyleBoxEditorPlugin(); }; #endif // STYLE_BOX_EDITOR_PLUGIN_H diff --git a/editor/plugins/sub_viewport_preview_editor_plugin.cpp b/editor/plugins/sub_viewport_preview_editor_plugin.cpp index 4498a1d64d..abf4ababe9 100644 --- a/editor/plugins/sub_viewport_preview_editor_plugin.cpp +++ b/editor/plugins/sub_viewport_preview_editor_plugin.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* sub_viewport_preview_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* sub_viewport_preview_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 "sub_viewport_preview_editor_plugin.h" @@ -39,11 +39,11 @@ void EditorInspectorPluginSubViewportPreview::parse_begin(Object *p_object) { TexturePreview *sub_viewport_preview = memnew(TexturePreview(sub_viewport->get_texture(), false)); // Otherwise `sub_viewport_preview`'s `texture_display` doesn't update properly when `sub_viewport`'s size changes. - sub_viewport->connect("size_changed", callable_mp((CanvasItem *)sub_viewport_preview->get_texture_display(), &CanvasItem::update)); + sub_viewport->connect("size_changed", callable_mp((CanvasItem *)sub_viewport_preview->get_texture_display(), &CanvasItem::queue_redraw)); add_custom_control(sub_viewport_preview); } -SubViewportPreviewEditorPlugin::SubViewportPreviewEditorPlugin(EditorNode *p_node) { +SubViewportPreviewEditorPlugin::SubViewportPreviewEditorPlugin() { Ref<EditorInspectorPluginSubViewportPreview> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/sub_viewport_preview_editor_plugin.h b/editor/plugins/sub_viewport_preview_editor_plugin.h index 7016910ebd..999d7d8b43 100644 --- a/editor/plugins/sub_viewport_preview_editor_plugin.h +++ b/editor/plugins/sub_viewport_preview_editor_plugin.h @@ -1,37 +1,36 @@ -/*************************************************************************/ -/* sub_viewport_preview_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* sub_viewport_preview_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 SUB_VIEWPORT_PREVIEW_EDITOR_PLUGIN_H #define SUB_VIEWPORT_PREVIEW_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/plugins/texture_editor_plugin.h" #include "scene/main/viewport.h" @@ -50,7 +49,7 @@ class SubViewportPreviewEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "SubViewportPreview"; } - SubViewportPreviewEditorPlugin(EditorNode *p_node); + SubViewportPreviewEditorPlugin(); }; #endif // SUB_VIEWPORT_PREVIEW_EDITOR_PLUGIN_H diff --git a/editor/plugins/text_control_editor_plugin.cpp b/editor/plugins/text_control_editor_plugin.cpp deleted file mode 100644 index bef93c161a..0000000000 --- a/editor/plugins/text_control_editor_plugin.cpp +++ /dev/null @@ -1,375 +0,0 @@ -/*************************************************************************/ -/* text_control_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "text_control_editor_plugin.h" - -#include "editor/editor_scale.h" - -void TextControlEditor::_notification(int p_notification) { - switch (p_notification) { - case NOTIFICATION_ENTER_TREE: { - if (!EditorFileSystem::get_singleton()->is_connected("filesystem_changed", callable_mp(this, &TextControlEditor::_reload_fonts))) { - EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &TextControlEditor::_reload_fonts), make_binds("")); - } - [[fallthrough]]; - } - case NOTIFICATION_THEME_CHANGED: { - clear_formatting->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - } break; - case NOTIFICATION_EXIT_TREE: { - if (EditorFileSystem::get_singleton()->is_connected("filesystem_changed", callable_mp(this, &TextControlEditor::_reload_fonts))) { - EditorFileSystem::get_singleton()->disconnect("filesystem_changed", callable_mp(this, &TextControlEditor::_reload_fonts)); - } - } break; - default: - break; - } -} - -void TextControlEditor::_find_resources(EditorFileSystemDirectory *p_dir) { - for (int i = 0; i < p_dir->get_subdir_count(); i++) { - _find_resources(p_dir->get_subdir(i)); - } - - for (int i = 0; i < p_dir->get_file_count(); i++) { - if (p_dir->get_file_type(i) == "FontData") { - Ref<FontData> fd = ResourceLoader::load(p_dir->get_file_path(i)); - if (fd.is_valid()) { - String name = fd->get_font_name(); - String sty = fd->get_font_style_name(); - if (sty.is_empty()) { - sty = "Default"; - } - fonts[name][sty] = p_dir->get_file_path(i); - } - } - } -} - -void TextControlEditor::_reload_fonts(const String &p_path) { - fonts.clear(); - _find_resources(EditorFileSystem::get_singleton()->get_filesystem()); - _update_control(); -} - -void TextControlEditor::_update_fonts_menu() { - font_list->clear(); - font_list->add_item(TTR("[Theme Default]"), FONT_INFO_THEME_DEFAULT); - if (custom_font.is_valid()) { - font_list->add_item(TTR("[Custom Font]"), FONT_INFO_USER_CUSTOM); - } - - int id = FONT_INFO_ID; - for (Map<String, Map<String, String>>::Element *E = fonts.front(); E; E = E->next()) { - font_list->add_item(E->key(), id++); - } - - if (font_list->get_item_count() > 1) { - font_list->show(); - } else { - font_list->hide(); - } -} - -void TextControlEditor::_update_styles_menu() { - font_style_list->clear(); - if ((font_list->get_selected_id() >= FONT_INFO_ID)) { - const String &name = font_list->get_item_text(font_list->get_selected()); - for (Map<String, String>::Element *E = fonts[name].front(); E; E = E->next()) { - font_style_list->add_item(E->key()); - } - } else { - font_style_list->add_item("Default"); - } - - if (font_style_list->get_item_count() > 1) { - font_style_list->show(); - } else { - font_style_list->hide(); - } -} - -void TextControlEditor::_update_control() { - if (edited_control) { - // Get override names. - if (edited_control->is_class("RichTextLabel")) { - edited_color = "default_color"; - edited_font = "normal_font"; - edited_font_size = "normal_font_size"; - } else { - edited_color = "font_color"; - edited_font = "font"; - edited_font_size = "font_size"; - } - - // Get font override. - Ref<Font> font; - if (edited_control->has_theme_font_override(edited_font)) { - font = edited_control->get_theme_font(edited_font); - } - if (font.is_valid()) { - if (font->get_data_count() != 1) { - // Composite font, save it to "custom_font" to allow undoing font change. - custom_font = font; - _update_fonts_menu(); - font_list->select(FONT_INFO_USER_CUSTOM); - _update_styles_menu(); - font_style_list->select(0); - } else { - // Single face font, search for the font with matching name and style. - String name = font->get_data(0)->get_font_name(); - String style = font->get_data(0)->get_font_style_name(); - if (fonts.has(name) && fonts[name].has(style)) { - _update_fonts_menu(); - for (int i = 0; i < font_list->get_item_count(); i++) { - if (font_list->get_item_text(i) == name) { - font_list->select(i); - break; - } - } - _update_styles_menu(); - for (int i = 0; i < font_style_list->get_item_count(); i++) { - if (font_style_list->get_item_text(i) == style) { - font_style_list->select(i); - break; - } - } - } else { - // Unknown font, save it to "custom_font" to allow undoing font change. - custom_font = font; - _update_fonts_menu(); - font_list->select(FONT_INFO_USER_CUSTOM); - _update_styles_menu(); - font_style_list->select(0); - } - } - } else { - // No font override, select "Theme Default". - _update_fonts_menu(); - font_list->select(FONT_INFO_THEME_DEFAULT); - _update_styles_menu(); - font_style_list->select(0); - } - - // Get other theme overrides. - font_size_list->set_value(edited_control->get_theme_font_size(edited_font_size)); - outline_size_list->set_value(edited_control->get_theme_constant("outline_size")); - - font_color_picker->set_pick_color(edited_control->get_theme_color(edited_color)); - outline_color_picker->set_pick_color(edited_control->get_theme_color("font_outline_color")); - } -} - -void TextControlEditor::_font_selected(int p_id) { - _update_styles_menu(); - _set_font(); -} - -void TextControlEditor::_font_style_selected(int p_id) { - _set_font(); -} - -void TextControlEditor::_set_font() { - if (edited_control) { - if (font_list->get_selected_id() == FONT_INFO_THEME_DEFAULT) { - // Remove font override. - edited_control->remove_theme_font_override(edited_font); - return; - } else if (font_list->get_selected_id() == FONT_INFO_USER_CUSTOM) { - // Restore "custom_font". - edited_control->add_theme_font_override(edited_font, custom_font); - return; - } else { - // Load new font resource using selected name and style. - String name = font_list->get_item_text(font_list->get_selected()); - String sty = font_style_list->get_item_text(font_style_list->get_selected()); - if (sty.is_empty()) { - sty = "Default"; - } - if (fonts.has(name)) { - Ref<FontData> fd = ResourceLoader::load(fonts[name][sty]); - if (fd.is_valid()) { - Ref<Font> f; - f.instantiate(); - f->add_data(fd); - edited_control->add_theme_font_override(edited_font, f); - } - } - } - } -} - -void TextControlEditor::_font_size_selected(double p_size) { - if (edited_control) { - edited_control->add_theme_font_size_override(edited_font_size, p_size); - } -} - -void TextControlEditor::_outline_size_selected(double p_size) { - if (edited_control) { - edited_control->add_theme_constant_override("outline_size", p_size); - } -} - -void TextControlEditor::_font_color_changed(const Color &p_color) { - if (edited_control) { - edited_control->add_theme_color_override(edited_color, p_color); - } -} - -void TextControlEditor::_outline_color_changed(const Color &p_color) { - if (edited_control) { - edited_control->add_theme_color_override("font_outline_color", p_color); - } -} - -void TextControlEditor::_clear_formatting() { - if (edited_control) { - edited_control->begin_bulk_theme_override(); - edited_control->remove_theme_font_override(edited_font); - edited_control->remove_theme_font_size_override(edited_font_size); - edited_control->remove_theme_color_override(edited_color); - edited_control->remove_theme_color_override("font_outline_color"); - edited_control->remove_theme_constant_override("outline_size"); - edited_control->end_bulk_theme_override(); - _update_control(); - } -} - -void TextControlEditor::edit(Object *p_object) { - Control *ctrl = Object::cast_to<Control>(p_object); - if (!ctrl) { - edited_control = nullptr; - custom_font = Ref<Font>(); - } else { - edited_control = ctrl; - custom_font = Ref<Font>(); - _update_control(); - } -} - -bool TextControlEditor::handles(Object *p_object) const { - Control *ctrl = Object::cast_to<Control>(p_object); - if (!ctrl) { - return false; - } else { - bool valid = false; - ctrl->get("text", &valid); - return valid; - } -} - -TextControlEditor::TextControlEditor() { - add_child(memnew(VSeparator)); - - font_list = memnew(OptionButton); - font_list->set_flat(true); - font_list->set_tooltip(TTR("Font")); - add_child(font_list); - font_list->connect("item_selected", callable_mp(this, &TextControlEditor::_font_selected)); - - font_style_list = memnew(OptionButton); - font_style_list->set_flat(true); - font_style_list->set_tooltip(TTR("Font style")); - font_style_list->set_toggle_mode(true); - add_child(font_style_list); - font_style_list->connect("item_selected", callable_mp(this, &TextControlEditor::_font_style_selected)); - - font_size_list = memnew(SpinBox); - font_size_list->set_tooltip(TTR("Font Size")); - font_size_list->get_line_edit()->add_theme_constant_override("minimum_character_width", 2); - font_size_list->set_min(6); - font_size_list->set_step(1); - font_size_list->set_max(96); - font_size_list->get_line_edit()->set_flat(true); - add_child(font_size_list); - font_size_list->connect("value_changed", callable_mp(this, &TextControlEditor::_font_size_selected)); - - font_color_picker = memnew(ColorPickerButton); - font_color_picker->set_custom_minimum_size(Size2(20, 0) * EDSCALE); - font_color_picker->set_flat(true); - font_color_picker->set_tooltip(TTR("Text Color")); - add_child(font_color_picker); - font_color_picker->connect("color_changed", callable_mp(this, &TextControlEditor::_font_color_changed)); - - add_child(memnew(VSeparator)); - - outline_size_list = memnew(SpinBox); - outline_size_list->set_tooltip(TTR("Outline Size")); - outline_size_list->get_line_edit()->add_theme_constant_override("minimum_character_width", 2); - outline_size_list->set_min(0); - outline_size_list->set_step(1); - outline_size_list->set_max(96); - outline_size_list->get_line_edit()->set_flat(true); - add_child(outline_size_list); - outline_size_list->connect("value_changed", callable_mp(this, &TextControlEditor::_outline_size_selected)); - - outline_color_picker = memnew(ColorPickerButton); - outline_color_picker->set_custom_minimum_size(Size2(20, 0) * EDSCALE); - outline_color_picker->set_flat(true); - outline_color_picker->set_tooltip(TTR("Outline Color")); - add_child(outline_color_picker); - outline_color_picker->connect("color_changed", callable_mp(this, &TextControlEditor::_outline_color_changed)); - - add_child(memnew(VSeparator)); - - clear_formatting = memnew(Button); - clear_formatting->set_flat(true); - clear_formatting->set_tooltip(TTR("Clear Formatting")); - add_child(clear_formatting); - clear_formatting->connect("pressed", callable_mp(this, &TextControlEditor::_clear_formatting)); -} - -/*************************************************************************/ - -void TextControlEditorPlugin::edit(Object *p_object) { - text_ctl_editor->edit(p_object); -} - -bool TextControlEditorPlugin::handles(Object *p_object) const { - return text_ctl_editor->handles(p_object); -} - -void TextControlEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - text_ctl_editor->show(); - } else { - text_ctl_editor->hide(); - text_ctl_editor->edit(nullptr); - } -} - -TextControlEditorPlugin::TextControlEditorPlugin(EditorNode *p_node) { - editor = p_node; - text_ctl_editor = memnew(TextControlEditor); - CanvasItemEditor::get_singleton()->add_control_to_menu_panel(text_ctl_editor); - - text_ctl_editor->hide(); -} diff --git a/editor/plugins/text_control_editor_plugin.h b/editor/plugins/text_control_editor_plugin.h deleted file mode 100644 index d3a4ff5ef9..0000000000 --- a/editor/plugins/text_control_editor_plugin.h +++ /dev/null @@ -1,119 +0,0 @@ -/*************************************************************************/ -/* text_control_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 TEXT_CONTROL_EDITOR_PLUGIN_H -#define TEXT_CONTROL_EDITOR_PLUGIN_H - -#include "canvas_item_editor_plugin.h" -#include "editor/editor_file_system.h" -#include "editor/editor_inspector.h" -#include "editor/editor_node.h" -#include "editor/editor_plugin.h" -#include "scene/gui/color_rect.h" -#include "scene/gui/menu_button.h" -#include "scene/gui/option_button.h" -#include "scene/gui/popup_menu.h" - -/*************************************************************************/ - -class TextControlEditor : public HBoxContainer { - GDCLASS(TextControlEditor, HBoxContainer); - - enum FontInfoID { - FONT_INFO_THEME_DEFAULT = 0, - FONT_INFO_USER_CUSTOM = 1, - FONT_INFO_ID = 100, - }; - - Map<String, Map<String, String>> fonts; - - OptionButton *font_list = nullptr; - SpinBox *font_size_list = nullptr; - OptionButton *font_style_list = nullptr; - ColorPickerButton *font_color_picker = nullptr; - SpinBox *outline_size_list = nullptr; - ColorPickerButton *outline_color_picker = nullptr; - Button *clear_formatting = nullptr; - - Control *edited_control = nullptr; - String edited_color; - String edited_font; - String edited_font_size; - Ref<Font> custom_font; - -protected: - void _notification(int p_notification); - static void _bind_methods(){}; - - void _find_resources(EditorFileSystemDirectory *p_dir); - void _reload_fonts(const String &p_path); - - void _update_fonts_menu(); - void _update_styles_menu(); - void _update_control(); - - void _font_selected(int p_id); - void _font_style_selected(int p_id); - void _set_font(); - - void _font_size_selected(double p_size); - void _outline_size_selected(double p_size); - - void _font_color_changed(const Color &p_color); - void _outline_color_changed(const Color &p_color); - - void _clear_formatting(); - -public: - void edit(Object *p_object); - bool handles(Object *p_object) const; - - TextControlEditor(); -}; - -/*************************************************************************/ - -class TextControlEditorPlugin : public EditorPlugin { - GDCLASS(TextControlEditorPlugin, EditorPlugin); - - TextControlEditor *text_ctl_editor; - EditorNode *editor; - -public: - virtual String get_name() const override { return "TextControlFontEditor"; } - bool has_main_screen() const override { return false; } - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - TextControlEditorPlugin(EditorNode *p_node); -}; - -#endif // TEXT_CONTROL_EDITOR_PLUGIN_H diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 12d13571f8..ceb170d7d8 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -1,37 +1,40 @@ -/*************************************************************************/ -/* text_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* text_editor.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 "text_editor.h" +#include "core/io/json.h" #include "core/os/keyboard.h" #include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "scene/gui/menu_button.h" void TextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { ERR_FAIL_COND(p_highlighter.is_null()); @@ -43,11 +46,11 @@ void TextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlight void TextEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { ERR_FAIL_COND(p_highlighter.is_null()); - Map<String, Ref<EditorSyntaxHighlighter>>::Element *el = highlighters.front(); - while (el != nullptr) { - int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key()); - highlighter_menu->set_item_checked(highlighter_index, el->value() == p_highlighter); - el = el->next(); + HashMap<String, Ref<EditorSyntaxHighlighter>>::Iterator el = highlighters.begin(); + while (el) { + int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key); + highlighter_menu->set_item_checked(highlighter_index, el->value == p_highlighter); + ++el; } CodeEdit *te = code_editor->get_text_editor(); @@ -65,12 +68,12 @@ void TextEditor::_load_theme_settings() { String TextEditor::get_name() { String name; - name = text_file->get_path().get_file(); + name = edited_res->get_path().get_file(); if (name.is_empty()) { // This appears for newly created built-in text_files before saving the scene. name = TTR("[unsaved]"); - } else if (text_file->is_built_in()) { - const String &text_file_name = text_file->get_name(); + } else if (edited_res->is_built_in()) { + const String &text_file_name = edited_res->get_name(); if (!text_file_name.is_empty()) { // If the built-in text_file has a custom resource name defined, // display the built-in text_file name as follows: `ResourceName (scene_file.tscn)` @@ -86,20 +89,29 @@ String TextEditor::get_name() { } Ref<Texture2D> TextEditor::get_theme_icon() { - return EditorNode::get_singleton()->get_object_icon(text_file.ptr(), ""); + return EditorNode::get_singleton()->get_object_icon(edited_res.ptr(), "TextFile"); } -RES TextEditor::get_edited_resource() const { - return text_file; +Ref<Resource> TextEditor::get_edited_resource() const { + return edited_res; } -void TextEditor::set_edited_resource(const RES &p_res) { - ERR_FAIL_COND(text_file.is_valid()); +void TextEditor::set_edited_resource(const Ref<Resource> &p_res) { + ERR_FAIL_COND(edited_res.is_valid()); ERR_FAIL_COND(p_res.is_null()); - text_file = p_res; + edited_res = p_res; + + Ref<TextFile> text_file = edited_res; + if (text_file != nullptr) { + code_editor->get_text_editor()->set_text(text_file->get_text()); + } + + Ref<JSON> json_file = edited_res; + if (json_file != nullptr) { + code_editor->get_text_editor()->set_text(json_file->get_parsed_text()); + } - code_editor->get_text_editor()->set_text(text_file->get_text()); code_editor->get_text_editor()->clear_undo_history(); code_editor->get_text_editor()->tag_saved_version(); @@ -107,7 +119,7 @@ void TextEditor::set_edited_resource(const RES &p_res) { code_editor->update_line_and_column(); } -void TextEditor::enable_editor() { +void TextEditor::enable_editor(Control *p_shortcut_context) { if (editor_enabled) { return; } @@ -115,6 +127,17 @@ void TextEditor::enable_editor() { editor_enabled = true; _load_theme_settings(); + + _validate_script(); + + if (p_shortcut_context) { + for (int i = 0; i < edit_hb->get_child_count(); ++i) { + Control *c = cast_to<Control>(edit_hb->get_child(i)); + if (c) { + c->set_shortcut_context(p_shortcut_context); + } + } + } } void TextEditor::add_callback(const String &p_function, PackedStringArray p_args) { @@ -127,12 +150,12 @@ Control *TextEditor::get_base_editor() const { return code_editor->get_text_editor(); } -Array TextEditor::get_breakpoints() { - return Array(); +PackedInt32Array TextEditor::get_breakpoints() { + return PackedInt32Array(); } void TextEditor::reload_text() { - ERR_FAIL_COND(text_file.is_null()); + ERR_FAIL_COND(edited_res.is_null()); CodeEdit *te = code_editor->get_text_editor(); int column = te->get_caret_column(); @@ -140,7 +163,16 @@ void TextEditor::reload_text() { int h = te->get_h_scroll(); int v = te->get_v_scroll(); - te->set_text(text_file->get_text()); + Ref<TextFile> text_file = edited_res; + if (text_file != nullptr) { + te->set_text(text_file->get_text()); + } + + Ref<JSON> json_file = edited_res; + if (json_file != nullptr) { + te->set_text(json_file->get_parsed_text()); + } + te->set_caret_line(row); te->set_caret_column(column); te->set_h_scroll(h); @@ -149,11 +181,26 @@ void TextEditor::reload_text() { te->tag_saved_version(); code_editor->update_line_and_column(); + _validate_script(); } void TextEditor::_validate_script() { emit_signal(SNAME("name_changed")); emit_signal(SNAME("edited_script_changed")); + + Ref<JSON> json_file = edited_res; + if (json_file != nullptr) { + CodeEdit *te = code_editor->get_text_editor(); + + te->set_line_background_color(code_editor->get_error_pos().x, Color(0, 0, 0, 0)); + code_editor->set_error(""); + + if (json_file->parse(te->get_text(), true) != OK) { + code_editor->set_error(json_file->get_error_message()); + code_editor->set_error_pos(json_file->get_error_line(), 0); + te->set_line_background_color(code_editor->get_error_pos().x, EDITOR_GET("text_editor/theme/highlighting/mark_color")); + } + } } void TextEditor::_update_bookmark_list() { @@ -164,7 +211,7 @@ void TextEditor::_update_bookmark_list() { bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT); bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV); - Array bookmark_list = code_editor->get_text_editor()->get_bookmarked_lines(); + PackedInt32Array bookmark_list = code_editor->get_text_editor()->get_bookmarked_lines(); if (bookmark_list.size() == 0) { return; } @@ -179,7 +226,7 @@ void TextEditor::_update_bookmark_list() { } bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\""); - bookmarks_menu->set_item_metadata(bookmarks_menu->get_item_count() - 1, bookmark_list[i]); + bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); } } @@ -192,13 +239,22 @@ void TextEditor::_bookmark_item_pressed(int p_idx) { } void TextEditor::apply_code() { - text_file->set_text(code_editor->get_text_editor()->get_text()); + Ref<TextFile> text_file = edited_res; + if (text_file != nullptr) { + text_file->set_text(code_editor->get_text_editor()->get_text()); + } + + Ref<JSON> json_file = edited_res; + if (json_file != nullptr) { + json_file->parse(code_editor->get_text_editor()->get_text(), true); + } + code_editor->get_text_editor()->get_syntax_highlighter()->update_cache(); } bool TextEditor::is_unsaved() { const bool unsaved = code_editor->get_text_editor()->get_version() != code_editor->get_text_editor()->get_saved_version() || - text_file->get_path().is_empty(); // In memory. + edited_res->get_path().is_empty(); // In memory. return unsaved; } @@ -220,6 +276,10 @@ void TextEditor::set_edit_state(const Variant &p_state) { ensure_focus(); } +Variant TextEditor::get_navigation_state() { + return code_editor->get_navigation_state(); +} + void TextEditor::trim_trailing_whitespace() { code_editor->trim_trailing_whitespace(); } @@ -272,8 +332,10 @@ void TextEditor::update_settings() { code_editor->update_editor_settings(); } -void TextEditor::set_tooltip_request_func(String p_method, Object *p_obj) { - code_editor->get_text_editor()->set_tooltip_request_func(p_obj, p_method, this); +void TextEditor::set_tooltip_request_func(const Callable &p_toolip_callback) { + Variant args[1] = { this }; + const Variant *argp[] = { &args[0] }; + code_editor->get_text_editor()->set_tooltip_request_func(p_toolip_callback.bindp(argp, 1)); } Control *TextEditor::get_edit_menu() { @@ -322,12 +384,12 @@ void TextEditor::_edit_option(int p_op) { case EDIT_MOVE_LINE_DOWN: { code_editor->move_lines_down(); } break; - case EDIT_INDENT_LEFT: { - tx->unindent_lines(); - } break; - case EDIT_INDENT_RIGHT: { + case EDIT_INDENT: { tx->indent_lines(); } break; + case EDIT_UNINDENT: { + tx->unindent_lines(); + } break; case EDIT_DELETE_LINE: { code_editor->delete_lines(); } break; @@ -335,16 +397,18 @@ void TextEditor::_edit_option(int p_op) { code_editor->duplicate_selection(); } break; case EDIT_TOGGLE_FOLD_LINE: { - tx->toggle_foldable_line(tx->get_caret_line()); - tx->update(); + for (int caret_idx = 0; caret_idx < tx->get_caret_count(); caret_idx++) { + tx->toggle_foldable_line(tx->get_caret_line(caret_idx)); + } + tx->queue_redraw(); } break; case EDIT_FOLD_ALL_LINES: { tx->fold_all_lines(); - tx->update(); + tx->queue_redraw(); } break; case EDIT_UNFOLD_ALL_LINES: { tx->unfold_all_lines(); - tx->update(); + tx->queue_redraw(); } break; case EDIT_TRIM_TRAILING_WHITESAPCE: { trim_trailing_whitespace(); @@ -410,8 +474,8 @@ void TextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { code_editor->convert_case(p_case); } -static ScriptEditorBase *create_editor(const RES &p_resource) { - if (Object::cast_to<TextFile>(*p_resource)) { +static ScriptEditorBase *create_editor(const Ref<Resource> &p_resource) { + if (Object::cast_to<TextFile>(*p_resource) || Object::cast_to<JSON>(*p_resource)) { return memnew(TextEditor); } return nullptr; @@ -432,11 +496,12 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { int row = pos.y; int col = pos.x; - tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click")); + tx->set_move_caret_on_right_click_enabled(EDITOR_GET("text_editor/behavior/navigation/move_caret_on_right_click")); bool can_fold = tx->can_fold_line(row); bool is_folded = tx->is_line_folded(row); if (tx->is_move_caret_on_right_click_enabled()) { + tx->remove_secondary_carets(); if (tx->has_selection()) { int from_line = tx->get_selection_from_line(); int to_line = tx->get_selection_to_line(); @@ -463,9 +528,9 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { Ref<InputEventKey> k = ev; if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) { CodeEdit *tx = code_editor->get_text_editor(); - int line = tx->get_caret_line(); - tx->adjust_viewport_to_caret(); - _make_context_menu(tx->has_selection(), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos())); + int line = tx->get_caret_line(0); + tx->adjust_viewport_to_caret(0); + _make_context_menu(tx->has_selection(0), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos(0))); context_menu->grab_focus(); } } @@ -490,8 +555,8 @@ void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO); context_menu->add_separator(); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_left"), EDIT_INDENT_LEFT); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); if (p_selection) { @@ -522,7 +587,7 @@ TextEditor::TextEditor() { code_editor->add_theme_constant_override("separation", 0); code_editor->connect("load_theme_settings", callable_mp(this, &TextEditor::_load_theme_settings)); code_editor->connect("validate_script", callable_mp(this, &TextEditor::_validate_script)); - code_editor->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + code_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); code_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); code_editor->show_toggle_scripts_button(); @@ -571,8 +636,8 @@ TextEditor::TextEditor() { edit_menu->get_popup()->add_separator(); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_left"), EDIT_INDENT_LEFT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES); @@ -628,8 +693,6 @@ TextEditor::TextEditor() { goto_line_dialog = memnew(GotoLineDialog); add_child(goto_line_dialog); - - code_editor->get_text_editor()->set_drag_forwarding(this); } TextEditor::~TextEditor() { @@ -637,4 +700,5 @@ TextEditor::~TextEditor() { } void TextEditor::validate() { + this->code_editor->validate_script(); } diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index d3fb0c0a16..85e0fee627 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -1,45 +1,47 @@ -/*************************************************************************/ -/* text_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* text_editor.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 TEXT_EDITOR_H #define TEXT_EDITOR_H #include "script_editor_plugin.h" +#include "editor/code_editor.h" + class TextEditor : public ScriptEditorBase { GDCLASS(TextEditor, ScriptEditorBase); private: CodeTextEditor *code_editor = nullptr; - Ref<TextFile> text_file; + Ref<Resource> edited_res; bool editor_enabled = false; HBoxContainer *edit_hb = nullptr; @@ -63,8 +65,8 @@ private: EDIT_CONVERT_INDENT_TO_TABS, EDIT_MOVE_LINE_UP, EDIT_MOVE_LINE_DOWN, - EDIT_INDENT_RIGHT, - EDIT_INDENT_LEFT, + EDIT_INDENT, + EDIT_UNINDENT, EDIT_DELETE_LINE, EDIT_DUPLICATE_SELECTION, EDIT_TO_UPPERCASE, @@ -92,7 +94,7 @@ protected: void _text_edit_gui_input(const Ref<InputEvent> &ev); void _prepare_edit_menu(); - Map<String, Ref<EditorSyntaxHighlighter>> highlighters; + HashMap<String, Ref<EditorSyntaxHighlighter>> highlighters; void _change_syntax_highlighter(int p_idx); void _load_theme_settings(); @@ -109,16 +111,17 @@ public: virtual String get_name() override; virtual Ref<Texture2D> get_theme_icon() override; - virtual RES get_edited_resource() const override; - virtual void set_edited_resource(const RES &p_res) override; - virtual void enable_editor() override; + virtual Ref<Resource> get_edited_resource() const override; + virtual void set_edited_resource(const Ref<Resource> &p_res) override; + virtual void enable_editor(Control *p_shortcut_context = nullptr) override; virtual void reload_text() override; virtual void apply_code() override; virtual bool is_unsaved() override; virtual Variant get_edit_state() override; virtual void set_edit_state(const Variant &p_state) override; + virtual Variant get_navigation_state() override; virtual Vector<String> get_functions() override; - virtual Array get_breakpoints() override; + virtual PackedInt32Array get_breakpoints() override; virtual void set_breakpoint(int p_line, bool p_enabled) override{}; virtual void clear_breakpoints() override{}; virtual void goto_line(int p_line, bool p_with_error = false) override; @@ -135,7 +138,7 @@ public: virtual bool show_members_overview() override; virtual bool can_lose_focus_on_node_selection() override { return true; } virtual void set_debugger_active(bool p_active) override; - virtual void set_tooltip_request_func(String p_method, Object *p_obj) override; + virtual void set_tooltip_request_func(const Callable &p_toolip_callback) override; virtual void add_callback(const String &p_function, PackedStringArray p_args) override; void update_toggle_scripts_button() override; diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp new file mode 100644 index 0000000000..ffd9564816 --- /dev/null +++ b/editor/plugins/text_shader_editor.cpp @@ -0,0 +1,1199 @@ +/**************************************************************************/ +/* text_shader_editor.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 "text_shader_editor.h" + +#include "core/version_generated.gen.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/filesystem_dock.h" +#include "editor/project_settings_editor.h" +#include "scene/gui/split_container.h" +#include "servers/rendering/shader_preprocessor.h" +#include "servers/rendering/shader_types.h" + +/*** SHADER SYNTAX HIGHLIGHTER ****/ + +Dictionary GDShaderSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) { + Dictionary color_map; + + for (const Point2i ®ion : disabled_branch_regions) { + if (p_line >= region.x && p_line <= region.y) { + Dictionary highlighter_info; + highlighter_info["color"] = disabled_branch_color; + + color_map[0] = highlighter_info; + return color_map; + } + } + + return CodeHighlighter::_get_line_syntax_highlighting_impl(p_line); +} + +void GDShaderSyntaxHighlighter::add_disabled_branch_region(const Point2i &p_region) { + ERR_FAIL_COND(p_region.x < 0); + ERR_FAIL_COND(p_region.y < 0); + + for (int i = 0; i < disabled_branch_regions.size(); i++) { + ERR_FAIL_COND_MSG(disabled_branch_regions[i].x == p_region.x, "Branch region with a start line '" + itos(p_region.x) + "' already exists."); + } + + Point2i disabled_branch_region; + disabled_branch_region.x = p_region.x; + disabled_branch_region.y = p_region.y; + disabled_branch_regions.push_back(disabled_branch_region); + + clear_highlighting_cache(); +} + +void GDShaderSyntaxHighlighter::clear_disabled_branch_regions() { + disabled_branch_regions.clear(); + clear_highlighting_cache(); +} + +void GDShaderSyntaxHighlighter::set_disabled_branch_color(const Color &p_color) { + disabled_branch_color = p_color; + clear_highlighting_cache(); +} + +/*** SHADER SCRIPT EDITOR ****/ + +static bool saved_warnings_enabled = false; +static bool saved_treat_warning_as_errors = false; +static HashMap<ShaderWarning::Code, bool> saved_warnings; +static uint32_t saved_warning_flags = 0U; + +void ShaderTextEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + if (is_visible_in_tree()) { + _load_theme_settings(); + if (warnings.size() > 0 && last_compile_result == OK) { + warnings_panel->clear(); + _update_warning_panel(); + } + } + } break; + } +} + +Ref<Shader> ShaderTextEditor::get_edited_shader() const { + return shader; +} + +Ref<ShaderInclude> ShaderTextEditor::get_edited_shader_include() const { + return shader_inc; +} + +void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) { + set_edited_shader(p_shader, p_shader->get_code()); +} + +void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader, const String &p_code) { + if (shader == p_shader) { + return; + } + if (shader.is_valid()) { + shader->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } + shader = p_shader; + shader_inc = Ref<ShaderInclude>(); + + set_edited_code(p_code); + + if (shader.is_valid()) { + shader->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } +} + +void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc) { + set_edited_shader_include(p_shader_inc, p_shader_inc->get_code()); +} + +void ShaderTextEditor::_shader_changed() { + // This function is used for dependencies (include changing changes main shader and forces it to revalidate) + if (block_shader_changed) { + return; + } + dependencies_version++; + _validate_script(); +} + +void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc, const String &p_code) { + if (shader_inc == p_shader_inc) { + return; + } + if (shader_inc.is_valid()) { + shader_inc->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } + shader_inc = p_shader_inc; + shader = Ref<Shader>(); + + set_edited_code(p_code); + + if (shader_inc.is_valid()) { + shader_inc->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } +} + +void ShaderTextEditor::set_edited_code(const String &p_code) { + _load_theme_settings(); + + get_text_editor()->set_text(p_code); + get_text_editor()->clear_undo_history(); + get_text_editor()->call_deferred(SNAME("set_h_scroll"), 0); + get_text_editor()->call_deferred(SNAME("set_v_scroll"), 0); + get_text_editor()->tag_saved_version(); + + _validate_script(); + _line_col_changed(); +} + +void ShaderTextEditor::reload_text() { + ERR_FAIL_COND(shader.is_null()); + + CodeEdit *te = get_text_editor(); + int column = te->get_caret_column(); + int row = te->get_caret_line(); + int h = te->get_h_scroll(); + int v = te->get_v_scroll(); + + te->set_text(shader->get_code()); + te->set_caret_line(row); + te->set_caret_column(column); + te->set_h_scroll(h); + te->set_v_scroll(v); + + te->tag_saved_version(); + + update_line_and_column(); +} + +void ShaderTextEditor::set_warnings_panel(RichTextLabel *p_warnings_panel) { + warnings_panel = p_warnings_panel; +} + +void ShaderTextEditor::_load_theme_settings() { + CodeEdit *te = get_text_editor(); + Color updated_marked_line_color = EDITOR_GET("text_editor/theme/highlighting/mark_color"); + if (updated_marked_line_color != marked_line_color) { + for (int i = 0; i < te->get_line_count(); i++) { + if (te->get_line_background_color(i) == marked_line_color) { + te->set_line_background_color(i, updated_marked_line_color); + } + } + marked_line_color = updated_marked_line_color; + } + + syntax_highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color")); + syntax_highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color")); + syntax_highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/function_color")); + syntax_highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/member_variable_color")); + + syntax_highlighter->clear_keyword_colors(); + + const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); + const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); + + List<String> keywords; + ShaderLanguage::get_keyword_list(&keywords); + + for (const String &E : keywords) { + if (ShaderLanguage::is_control_flow_keyword(E)) { + syntax_highlighter->add_keyword_color(E, control_flow_keyword_color); + } else { + syntax_highlighter->add_keyword_color(E, keyword_color); + } + } + + List<String> pp_keywords; + ShaderPreprocessor::get_keyword_list(&pp_keywords, false); + + for (const String &E : pp_keywords) { + syntax_highlighter->add_keyword_color(E, keyword_color); + } + + // Colorize built-ins like `COLOR` differently to make them easier + // to distinguish from keywords at a quick glance. + + List<String> built_ins; + + if (shader_inc.is_valid()) { + for (int i = 0; i < RenderingServer::SHADER_MAX; i++) { + for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(i))) { + for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) { + built_ins.push_back(F.key); + } + } + + const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(i)); + + for (int j = 0; j < modes.size(); j++) { + const ShaderLanguage::ModeInfo &mode_info = modes[j]; + + if (!mode_info.options.is_empty()) { + for (int k = 0; k < mode_info.options.size(); k++) { + built_ins.push_back(String(mode_info.name) + "_" + String(mode_info.options[k])); + } + } else { + built_ins.push_back(String(mode_info.name)); + } + } + } + } else if (shader.is_valid()) { + for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()))) { + for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) { + built_ins.push_back(F.key); + } + } + + const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); + + for (int i = 0; i < modes.size(); i++) { + const ShaderLanguage::ModeInfo &mode_info = modes[i]; + + if (!mode_info.options.is_empty()) { + for (int j = 0; j < mode_info.options.size(); j++) { + built_ins.push_back(String(mode_info.name) + "_" + String(mode_info.options[j])); + } + } else { + built_ins.push_back(String(mode_info.name)); + } + } + } + + const Color user_type_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color"); + + for (const String &E : built_ins) { + syntax_highlighter->add_keyword_color(E, user_type_color); + } + + // Colorize comments. + const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); + syntax_highlighter->clear_color_regions(); + syntax_highlighter->add_color_region("/*", "*/", comment_color, false); + syntax_highlighter->add_color_region("//", "", comment_color, true); + + // Disabled preprocessor branches use translucent text color to be easier to distinguish from comments. + syntax_highlighter->set_disabled_branch_color(Color(EDITOR_GET("text_editor/theme/highlighting/text_color")) * Color(1, 1, 1, 0.5)); + + te->clear_comment_delimiters(); + te->add_comment_delimiter("/*", "*/", false); + te->add_comment_delimiter("//", "", true); + + if (!te->has_auto_brace_completion_open_key("/*")) { + te->add_auto_brace_completion_pair("/*", "*/"); + } + + // Colorize preprocessor include strings. + const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); + syntax_highlighter->add_color_region("\"", "\"", string_color, false); + + if (warnings_panel) { + // Warnings panel. + warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts"))); + warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts"))); + } +} + +void ShaderTextEditor::_check_shader_mode() { + String type = ShaderLanguage::get_shader_type(get_text_editor()->get_text()); + + Shader::Mode mode; + + if (type == "canvas_item") { + mode = Shader::MODE_CANVAS_ITEM; + } else if (type == "particles") { + mode = Shader::MODE_PARTICLES; + } else if (type == "sky") { + mode = Shader::MODE_SKY; + } else if (type == "fog") { + mode = Shader::MODE_FOG; + } else { + mode = Shader::MODE_SPATIAL; + } + + if (shader->get_mode() != mode) { + set_block_shader_changed(true); + shader->set_code(get_text_editor()->get_text()); + set_block_shader_changed(false); + _load_theme_settings(); + } +} + +static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) { + RS::GlobalShaderParameterType gvt = RS::get_singleton()->global_shader_parameter_get_type(p_variable); + return (ShaderLanguage::DataType)RS::global_shader_uniform_type_get_shader_datatype(gvt); +} + +static String complete_from_path; + +static void _complete_include_paths_search(EditorFileSystemDirectory *p_efsd, List<ScriptLanguage::CodeCompletionOption> *r_options) { + if (!p_efsd) { + return; + } + for (int i = 0; i < p_efsd->get_file_count(); i++) { + if (p_efsd->get_file_type(i) == SNAME("ShaderInclude")) { + String path = p_efsd->get_file_path(i); + if (path.begins_with(complete_from_path)) { + path = path.replace_first(complete_from_path, ""); + } + r_options->push_back(ScriptLanguage::CodeCompletionOption(path, ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH)); + } + } + for (int j = 0; j < p_efsd->get_subdir_count(); j++) { + _complete_include_paths_search(p_efsd->get_subdir(j), r_options); + } +} + +static void _complete_include_paths(List<ScriptLanguage::CodeCompletionOption> *r_options) { + _complete_include_paths_search(EditorFileSystem::get_singleton()->get_filesystem(), r_options); +} + +void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) { + List<ScriptLanguage::CodeCompletionOption> pp_options; + List<ScriptLanguage::CodeCompletionOption> pp_defines; + ShaderPreprocessor preprocessor; + String code; + String resource_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path()); + complete_from_path = resource_path.get_base_dir(); + if (!complete_from_path.ends_with("/")) { + complete_from_path += "/"; + } + preprocessor.preprocess(p_code, resource_path, code, nullptr, nullptr, nullptr, nullptr, &pp_options, &pp_defines, _complete_include_paths); + complete_from_path = String(); + if (pp_options.size()) { + for (const ScriptLanguage::CodeCompletionOption &E : pp_options) { + r_options->push_back(E); + } + return; + } + for (const ScriptLanguage::CodeCompletionOption &E : pp_defines) { + r_options->push_back(E); + } + + ShaderLanguage sl; + String calltip; + ShaderLanguage::ShaderCompileInfo comp_info; + comp_info.global_shader_uniform_type_func = _get_global_shader_uniform_type; + + if (shader.is_null()) { + comp_info.is_include = true; + + sl.complete(code, comp_info, r_options, calltip); + get_text_editor()->set_code_hint(calltip); + return; + } + _check_shader_mode(); + comp_info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())); + comp_info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); + comp_info.shader_types = ShaderTypes::get_singleton()->get_types(); + + sl.complete(code, comp_info, r_options, calltip); + get_text_editor()->set_code_hint(calltip); +} + +void ShaderTextEditor::_validate_script() { + emit_signal(SNAME("script_changed")); // Ensure to notify that it changed, so it is applied + + String code; + + if (shader.is_valid()) { + _check_shader_mode(); + code = shader->get_code(); + } else { + code = shader_inc->get_code(); + } + + ShaderPreprocessor preprocessor; + String code_pp; + String error_pp; + List<ShaderPreprocessor::FilePosition> err_positions; + List<ShaderPreprocessor::Region> regions; + String filename; + if (shader.is_valid()) { + filename = shader->get_path(); + } else if (shader_inc.is_valid()) { + filename = shader_inc->get_path(); + } + last_compile_result = preprocessor.preprocess(code, filename, code_pp, &error_pp, &err_positions, ®ions); + + for (int i = 0; i < get_text_editor()->get_line_count(); i++) { + get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); + } + + syntax_highlighter->clear_disabled_branch_regions(); + for (const ShaderPreprocessor::Region ®ion : regions) { + if (!region.enabled) { + if (filename != region.file) { + continue; + } + syntax_highlighter->add_disabled_branch_region(Point2i(region.from_line, region.to_line)); + } + } + + set_error(""); + set_error_count(0); + + if (last_compile_result != OK) { + //preprocessor error + ERR_FAIL_COND(err_positions.size() == 0); + + String err_text = error_pp; + int err_line = err_positions.front()->get().line; + if (err_positions.size() == 1) { + // Error in main file + err_text = "error(" + itos(err_line) + "): " + err_text; + } else { + err_text = "error(" + itos(err_line) + ") in include " + err_positions.back()->get().file.get_file() + ":" + itos(err_positions.back()->get().line) + ": " + err_text; + set_error_count(err_positions.size() - 1); + } + + set_error(err_text); + set_error_pos(err_line - 1, 0); + for (int i = 0; i < get_text_editor()->get_line_count(); i++) { + get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); + } + get_text_editor()->set_line_background_color(err_line - 1, marked_line_color); + + set_warning_count(0); + + } else { + ShaderLanguage sl; + + sl.enable_warning_checking(saved_warnings_enabled); + uint32_t flags = saved_warning_flags; + if (shader.is_null()) { + if (flags & ShaderWarning::UNUSED_CONSTANT) { + flags &= ~(ShaderWarning::UNUSED_CONSTANT); + } + if (flags & ShaderWarning::UNUSED_FUNCTION) { + flags &= ~(ShaderWarning::UNUSED_FUNCTION); + } + if (flags & ShaderWarning::UNUSED_STRUCT) { + flags &= ~(ShaderWarning::UNUSED_STRUCT); + } + if (flags & ShaderWarning::UNUSED_UNIFORM) { + flags &= ~(ShaderWarning::UNUSED_UNIFORM); + } + if (flags & ShaderWarning::UNUSED_VARYING) { + flags &= ~(ShaderWarning::UNUSED_VARYING); + } + } + sl.set_warning_flags(flags); + + ShaderLanguage::ShaderCompileInfo comp_info; + comp_info.global_shader_uniform_type_func = _get_global_shader_uniform_type; + + if (shader.is_null()) { + comp_info.is_include = true; + } else { + Shader::Mode mode = shader->get_mode(); + comp_info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(mode)); + comp_info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(mode)); + comp_info.shader_types = ShaderTypes::get_singleton()->get_types(); + } + + code = code_pp; + //compiler error + last_compile_result = sl.compile(code, comp_info); + + if (last_compile_result != OK) { + String err_text; + int err_line; + Vector<ShaderLanguage::FilePosition> include_positions = sl.get_include_positions(); + if (include_positions.size() > 1) { + //error is in an include + err_line = include_positions[0].line; + err_text = "error(" + itos(err_line) + ") in include " + include_positions[include_positions.size() - 1].file + ":" + itos(include_positions[include_positions.size() - 1].line) + ": " + sl.get_error_text(); + set_error_count(include_positions.size() - 1); + } else { + err_line = sl.get_error_line(); + err_text = "error(" + itos(err_line) + "): " + sl.get_error_text(); + set_error_count(0); + } + set_error(err_text); + set_error_pos(err_line - 1, 0); + get_text_editor()->set_line_background_color(err_line - 1, marked_line_color); + } else { + set_error(""); + } + + if (warnings.size() > 0 || last_compile_result != OK) { + warnings_panel->clear(); + } + warnings.clear(); + for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) { + warnings.push_back(E->get()); + } + if (warnings.size() > 0 && last_compile_result == OK) { + warnings.sort_custom<WarningsComparator>(); + _update_warning_panel(); + } else { + set_warning_count(0); + } + } + + emit_signal(SNAME("script_validated"), last_compile_result == OK); // Notify that validation finished, to update the list of scripts +} + +void ShaderTextEditor::_update_warning_panel() { + int warning_count = 0; + + warnings_panel->push_table(2); + for (int i = 0; i < warnings.size(); i++) { + ShaderWarning &w = warnings[i]; + + if (warning_count == 0) { + if (saved_treat_warning_as_errors) { + String error_text = "error(" + itos(w.get_line()) + "): " + w.get_message() + " " + TTR("Warnings should be fixed to prevent errors."); + set_error_pos(w.get_line() - 1, 0); + set_error(error_text); + get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color); + } + } + + warning_count++; + int line = w.get_line(); + + // First cell. + warnings_panel->push_cell(); + warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + if (line != -1) { + warnings_panel->push_meta(line - 1); + warnings_panel->add_text(TTR("Line") + " " + itos(line)); + warnings_panel->add_text(" (" + w.get_name() + "):"); + warnings_panel->pop(); // Meta goto. + } else { + warnings_panel->add_text(w.get_name() + ":"); + } + warnings_panel->pop(); // Color. + warnings_panel->pop(); // Cell. + + // Second cell. + warnings_panel->push_cell(); + warnings_panel->add_text(w.get_message()); + warnings_panel->pop(); // Cell. + } + warnings_panel->pop(); // Table. + + set_warning_count(warning_count); +} + +void ShaderTextEditor::_bind_methods() { + ADD_SIGNAL(MethodInfo("script_validated", PropertyInfo(Variant::BOOL, "valid"))); +} + +ShaderTextEditor::ShaderTextEditor() { + syntax_highlighter.instantiate(); + get_text_editor()->set_syntax_highlighter(syntax_highlighter); +} + +/*** SCRIPT EDITOR ******/ + +void TextShaderEditor::_menu_option(int p_option) { + switch (p_option) { + case EDIT_UNDO: { + shader_editor->get_text_editor()->undo(); + } break; + case EDIT_REDO: { + shader_editor->get_text_editor()->redo(); + } break; + case EDIT_CUT: { + shader_editor->get_text_editor()->cut(); + } break; + case EDIT_COPY: { + shader_editor->get_text_editor()->copy(); + } break; + case EDIT_PASTE: { + shader_editor->get_text_editor()->paste(); + } break; + case EDIT_SELECT_ALL: { + shader_editor->get_text_editor()->select_all(); + } break; + case EDIT_MOVE_LINE_UP: { + shader_editor->move_lines_up(); + } break; + case EDIT_MOVE_LINE_DOWN: { + shader_editor->move_lines_down(); + } break; + case EDIT_INDENT: { + if (shader.is_null()) { + return; + } + shader_editor->get_text_editor()->indent_lines(); + } break; + case EDIT_UNINDENT: { + if (shader.is_null()) { + return; + } + shader_editor->get_text_editor()->unindent_lines(); + } break; + case EDIT_DELETE_LINE: { + shader_editor->delete_lines(); + } break; + case EDIT_DUPLICATE_SELECTION: { + shader_editor->duplicate_selection(); + } break; + case EDIT_TOGGLE_COMMENT: { + if (shader.is_null()) { + return; + } + + shader_editor->toggle_inline_comment("//"); + + } break; + case EDIT_COMPLETE: { + shader_editor->get_text_editor()->request_code_completion(); + } break; + case SEARCH_FIND: { + shader_editor->get_find_replace_bar()->popup_search(); + } break; + case SEARCH_FIND_NEXT: { + shader_editor->get_find_replace_bar()->search_next(); + } break; + case SEARCH_FIND_PREV: { + shader_editor->get_find_replace_bar()->search_prev(); + } break; + case SEARCH_REPLACE: { + shader_editor->get_find_replace_bar()->popup_replace(); + } break; + case SEARCH_GOTO_LINE: { + goto_line_dialog->popup_find_line(shader_editor->get_text_editor()); + } break; + case BOOKMARK_TOGGLE: { + shader_editor->toggle_bookmark(); + } break; + case BOOKMARK_GOTO_NEXT: { + shader_editor->goto_next_bookmark(); + } break; + case BOOKMARK_GOTO_PREV: { + shader_editor->goto_prev_bookmark(); + } break; + case BOOKMARK_REMOVE_ALL: { + shader_editor->remove_all_bookmarks(); + } break; + case HELP_DOCS: { + OS::get_singleton()->shell_open(vformat("%s/tutorials/shaders/shader_reference/index.html", VERSION_DOCS_URL)); + } break; + } + if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) { + shader_editor->get_text_editor()->call_deferred(SNAME("grab_focus")); + } +} + +void TextShaderEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + PopupMenu *popup = help_menu->get_popup(); + popup->set_item_icon(popup->get_item_index(HELP_DOCS), get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); + } break; + + case NOTIFICATION_APPLICATION_FOCUS_IN: { + _check_for_external_edit(); + } break; + } +} + +void TextShaderEditor::_editor_settings_changed() { + shader_editor->update_editor_settings(); + + shader_editor->get_text_editor()->add_theme_constant_override("line_spacing", EDITOR_GET("text_editor/appearance/whitespace/line_spacing")); + shader_editor->get_text_editor()->set_draw_breakpoints_gutter(false); + shader_editor->get_text_editor()->set_draw_executing_lines_gutter(false); +} + +void TextShaderEditor::_show_warnings_panel(bool p_show) { + warnings_panel->set_visible(p_show); +} + +void TextShaderEditor::_warning_clicked(Variant p_line) { + if (p_line.get_type() == Variant::INT) { + shader_editor->get_text_editor()->set_caret_line(p_line.operator int64_t()); + } +} + +void TextShaderEditor::_bind_methods() { + ClassDB::bind_method("_show_warnings_panel", &TextShaderEditor::_show_warnings_panel); + ClassDB::bind_method("_warning_clicked", &TextShaderEditor::_warning_clicked); + + ADD_SIGNAL(MethodInfo("validation_changed")); +} + +void TextShaderEditor::ensure_select_current() { +} + +void TextShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) { + shader_editor->goto_line_selection(p_line, p_begin, p_end); +} + +void TextShaderEditor::_project_settings_changed() { + _update_warnings(true); +} + +void TextShaderEditor::_update_warnings(bool p_validate) { + bool changed = false; + + bool warnings_enabled = GLOBAL_GET("debug/shader_language/warnings/enable").booleanize(); + if (warnings_enabled != saved_warnings_enabled) { + saved_warnings_enabled = warnings_enabled; + changed = true; + } + + bool treat_warning_as_errors = GLOBAL_GET("debug/shader_language/warnings/treat_warnings_as_errors").booleanize(); + if (treat_warning_as_errors != saved_treat_warning_as_errors) { + saved_treat_warning_as_errors = treat_warning_as_errors; + changed = true; + } + + bool update_flags = false; + + for (int i = 0; i < ShaderWarning::WARNING_MAX; i++) { + ShaderWarning::Code code = (ShaderWarning::Code)i; + bool value = GLOBAL_GET("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code(code).to_lower()); + + if (saved_warnings[code] != value) { + saved_warnings[code] = value; + update_flags = true; + changed = true; + } + } + + if (update_flags) { + saved_warning_flags = (uint32_t)ShaderWarning::get_flags_from_codemap(saved_warnings); + } + + if (p_validate && changed && shader_editor && shader_editor->get_edited_shader().is_valid()) { + shader_editor->validate_script(); + } +} + +void TextShaderEditor::_check_for_external_edit() { + bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change")); + + if (shader_inc.is_valid()) { + if (shader_inc->get_last_modified_time() != FileAccess::get_modified_time(shader_inc->get_path())) { + if (use_autoreload) { + _reload_shader_include_from_disk(); + } else { + disk_changed->call_deferred(SNAME("popup_centered")); + } + } + return; + } + + if (shader.is_null() || shader->is_built_in()) { + return; + } + + if (shader->get_last_modified_time() != FileAccess::get_modified_time(shader->get_path())) { + if (use_autoreload) { + _reload_shader_from_disk(); + } else { + disk_changed->call_deferred(SNAME("popup_centered")); + } + } +} + +void TextShaderEditor::_reload_shader_from_disk() { + Ref<Shader> rel_shader = ResourceLoader::load(shader->get_path(), shader->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_FAIL_COND(!rel_shader.is_valid()); + + shader_editor->set_block_shader_changed(true); + shader->set_code(rel_shader->get_code()); + shader_editor->set_block_shader_changed(false); + shader->set_last_modified_time(rel_shader->get_last_modified_time()); + shader_editor->reload_text(); +} + +void TextShaderEditor::_reload_shader_include_from_disk() { + Ref<ShaderInclude> rel_shader_include = ResourceLoader::load(shader_inc->get_path(), shader_inc->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_FAIL_COND(!rel_shader_include.is_valid()); + + shader_editor->set_block_shader_changed(true); + shader_inc->set_code(rel_shader_include->get_code()); + shader_editor->set_block_shader_changed(false); + shader_inc->set_last_modified_time(rel_shader_include->get_last_modified_time()); + shader_editor->reload_text(); +} + +void TextShaderEditor::_reload() { + if (shader.is_valid()) { + _reload_shader_from_disk(); + } else if (shader_inc.is_valid()) { + _reload_shader_include_from_disk(); + } +} + +void TextShaderEditor::edit(const Ref<Shader> &p_shader) { + if (p_shader.is_null() || !p_shader->is_text_shader()) { + return; + } + + if (shader == p_shader) { + return; + } + + shader = p_shader; + shader_inc = Ref<ShaderInclude>(); + + shader_editor->set_edited_shader(shader); +} + +void TextShaderEditor::edit(const Ref<ShaderInclude> &p_shader_inc) { + if (p_shader_inc.is_null()) { + return; + } + + if (shader_inc == p_shader_inc) { + return; + } + + shader_inc = p_shader_inc; + shader = Ref<Shader>(); + + shader_editor->set_edited_shader_include(p_shader_inc); +} + +void TextShaderEditor::save_external_data(const String &p_str) { + if (shader.is_null() && shader_inc.is_null()) { + disk_changed->hide(); + return; + } + + apply_shaders(); + + Ref<Shader> edited_shader = shader_editor->get_edited_shader(); + if (edited_shader.is_valid()) { + ResourceSaver::save(edited_shader); + } + if (shader.is_valid() && shader != edited_shader) { + ResourceSaver::save(shader); + } + + Ref<ShaderInclude> edited_shader_inc = shader_editor->get_edited_shader_include(); + if (edited_shader_inc.is_valid()) { + ResourceSaver::save(edited_shader_inc); + } + if (shader_inc.is_valid() && shader_inc != edited_shader_inc) { + ResourceSaver::save(shader_inc); + } + shader_editor->get_text_editor()->tag_saved_version(); + + disk_changed->hide(); +} + +void TextShaderEditor::validate_script() { + shader_editor->_validate_script(); +} + +bool TextShaderEditor::is_unsaved() const { + return shader_editor->get_text_editor()->get_saved_version() != shader_editor->get_text_editor()->get_version(); +} + +void TextShaderEditor::tag_saved_version() { + shader_editor->get_text_editor()->tag_saved_version(); +} + +void TextShaderEditor::apply_shaders() { + String editor_code = shader_editor->get_text_editor()->get_text(); + if (shader.is_valid()) { + String shader_code = shader->get_code(); + if (shader_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) { + shader_editor->set_block_shader_changed(true); + shader->set_code(editor_code); + shader_editor->set_block_shader_changed(false); + shader->set_edited(true); + } + } + if (shader_inc.is_valid()) { + String shader_inc_code = shader_inc->get_code(); + if (shader_inc_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) { + shader_editor->set_block_shader_changed(true); + shader_inc->set_code(editor_code); + shader_editor->set_block_shader_changed(false); + shader_inc->set_edited(true); + } + } + + dependencies_version = shader_editor->get_dependencies_version(); +} + +void TextShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { + Ref<InputEventMouseButton> mb = ev; + + if (mb.is_valid()) { + if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { + CodeEdit *tx = shader_editor->get_text_editor(); + + Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position()); + int row = pos.y; + int col = pos.x; + tx->set_move_caret_on_right_click_enabled(EDITOR_GET("text_editor/behavior/navigation/move_caret_on_right_click")); + + if (tx->is_move_caret_on_right_click_enabled()) { + tx->remove_secondary_carets(); + if (tx->has_selection()) { + int from_line = tx->get_selection_from_line(); + int to_line = tx->get_selection_to_line(); + int from_column = tx->get_selection_from_column(); + int to_column = tx->get_selection_to_column(); + + if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { + // Right click is outside the selected text + tx->deselect(); + } + } + if (!tx->has_selection()) { + tx->set_caret_line(row, true, false); + tx->set_caret_column(col); + } + } + _make_context_menu(tx->has_selection(), get_local_mouse_position()); + } + } + + Ref<InputEventKey> k = ev; + if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) { + CodeEdit *tx = shader_editor->get_text_editor(); + tx->adjust_viewport_to_caret(); + _make_context_menu(tx->has_selection(), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos())); + context_menu->grab_focus(); + } +} + +void TextShaderEditor::_update_bookmark_list() { + bookmarks_menu->clear(); + + bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); + bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL); + bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT); + bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV); + + PackedInt32Array bookmark_list = shader_editor->get_text_editor()->get_bookmarked_lines(); + if (bookmark_list.size() == 0) { + return; + } + + bookmarks_menu->add_separator(); + + for (int i = 0; i < bookmark_list.size(); i++) { + String line = shader_editor->get_text_editor()->get_line(bookmark_list[i]).strip_edges(); + // Limit the size of the line if too big. + if (line.length() > 50) { + line = line.substr(0, 50); + } + + bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\""); + bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); + } +} + +void TextShaderEditor::_bookmark_item_pressed(int p_idx) { + if (p_idx < 4) { // Any item before the separator. + _menu_option(bookmarks_menu->get_item_id(p_idx)); + } else { + shader_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx)); + } +} + +void TextShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) { + context_menu->clear(); + if (p_selection) { + context_menu->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT); + context_menu->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY); + } + + context_menu->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE); + context_menu->add_separator(); + context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL); + context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); + context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO); + + context_menu->add_separator(); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); + + context_menu->set_position(get_screen_position() + p_position); + context_menu->reset_size(); + context_menu->popup(); +} + +TextShaderEditor::TextShaderEditor() { + GLOBAL_DEF("debug/shader_language/warnings/enable", true); + GLOBAL_DEF("debug/shader_language/warnings/treat_warnings_as_errors", false); + for (int i = 0; i < (int)ShaderWarning::WARNING_MAX; i++) { + GLOBAL_DEF("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code((ShaderWarning::Code)i).to_lower(), true); + } + _update_warnings(false); + + shader_editor = memnew(ShaderTextEditor); + + shader_editor->connect("script_validated", callable_mp(this, &TextShaderEditor::_script_validated)); + + shader_editor->set_v_size_flags(SIZE_EXPAND_FILL); + shader_editor->add_theme_constant_override("separation", 0); + shader_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + + shader_editor->connect("show_warnings_panel", callable_mp(this, &TextShaderEditor::_show_warnings_panel)); + shader_editor->connect("script_changed", callable_mp(this, &TextShaderEditor::apply_shaders)); + EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextShaderEditor::_editor_settings_changed)); + ProjectSettingsEditor::get_singleton()->connect("confirmed", callable_mp(this, &TextShaderEditor::_project_settings_changed)); + + shader_editor->get_text_editor()->set_code_hint_draw_below(EDITOR_GET("text_editor/completion/put_callhint_tooltip_below_current_line")); + + shader_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true); + shader_editor->get_text_editor()->set_context_menu_enabled(false); + shader_editor->get_text_editor()->connect("gui_input", callable_mp(this, &TextShaderEditor::_text_edit_gui_input)); + + shader_editor->update_editor_settings(); + + context_menu = memnew(PopupMenu); + add_child(context_menu); + context_menu->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option)); + + VBoxContainer *main_container = memnew(VBoxContainer); + HBoxContainer *hbc = memnew(HBoxContainer); + + edit_menu = memnew(MenuButton); + edit_menu->set_shortcut_context(this); + edit_menu->set_text(TTR("Edit")); + edit_menu->set_switch_on_hover(true); + + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE); + edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option)); + + search_menu = memnew(MenuButton); + search_menu->set_shortcut_context(this); + search_menu->set_text(TTR("Search")); + search_menu->set_switch_on_hover(true); + + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); + search_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option)); + + MenuButton *goto_menu = memnew(MenuButton); + goto_menu->set_shortcut_context(this); + goto_menu->set_text(TTR("Go To")); + goto_menu->set_switch_on_hover(true); + goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option)); + + goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE); + goto_menu->get_popup()->add_separator(); + + bookmarks_menu = memnew(PopupMenu); + bookmarks_menu->set_name("Bookmarks"); + goto_menu->get_popup()->add_child(bookmarks_menu); + goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks"); + _update_bookmark_list(); + bookmarks_menu->connect("about_to_popup", callable_mp(this, &TextShaderEditor::_update_bookmark_list)); + bookmarks_menu->connect("index_pressed", callable_mp(this, &TextShaderEditor::_bookmark_item_pressed)); + + help_menu = memnew(MenuButton); + help_menu->set_text(TTR("Help")); + help_menu->set_switch_on_hover(true); + help_menu->get_popup()->add_item(TTR("Online Docs"), HELP_DOCS); + help_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextShaderEditor::_menu_option)); + + add_child(main_container); + main_container->add_child(hbc); + hbc->add_child(search_menu); + hbc->add_child(edit_menu); + hbc->add_child(goto_menu); + hbc->add_child(help_menu); + hbc->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles"))); + + VSplitContainer *editor_box = memnew(VSplitContainer); + main_container->add_child(editor_box); + editor_box->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + editor_box->set_v_size_flags(SIZE_EXPAND_FILL); + editor_box->add_child(shader_editor); + + FindReplaceBar *bar = memnew(FindReplaceBar); + main_container->add_child(bar); + bar->hide(); + shader_editor->set_find_replace_bar(bar); + + warnings_panel = memnew(RichTextLabel); + warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); + warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL); + warnings_panel->set_meta_underline(true); + warnings_panel->set_selection_enabled(true); + warnings_panel->set_focus_mode(FOCUS_CLICK); + warnings_panel->hide(); + warnings_panel->connect("meta_clicked", callable_mp(this, &TextShaderEditor::_warning_clicked)); + editor_box->add_child(warnings_panel); + shader_editor->set_warnings_panel(warnings_panel); + + goto_line_dialog = memnew(GotoLineDialog); + add_child(goto_line_dialog); + + disk_changed = memnew(ConfirmationDialog); + + VBoxContainer *vbc = memnew(VBoxContainer); + disk_changed->add_child(vbc); + + Label *dl = memnew(Label); + dl->set_text(TTR("This shader has been modified on disk.\nWhat action should be taken?")); + vbc->add_child(dl); + + disk_changed->connect("confirmed", callable_mp(this, &TextShaderEditor::_reload)); + disk_changed->set_ok_button_text(TTR("Reload")); + + disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); + disk_changed->connect("custom_action", callable_mp(this, &TextShaderEditor::save_external_data)); + + add_child(disk_changed); + + _editor_settings_changed(); +} diff --git a/editor/plugins/text_shader_editor.h b/editor/plugins/text_shader_editor.h new file mode 100644 index 0000000000..edf92d8a7b --- /dev/null +++ b/editor/plugins/text_shader_editor.h @@ -0,0 +1,200 @@ +/**************************************************************************/ +/* text_shader_editor.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 TEXT_SHADER_EDITOR_H +#define TEXT_SHADER_EDITOR_H + +#include "editor/code_editor.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/rich_text_label.h" +#include "servers/rendering/shader_warnings.h" + +class GDShaderSyntaxHighlighter : public CodeHighlighter { + GDCLASS(GDShaderSyntaxHighlighter, CodeHighlighter) + +private: + Vector<Point2i> disabled_branch_regions; + Color disabled_branch_color; + +public: + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override; + + void add_disabled_branch_region(const Point2i &p_region); + void clear_disabled_branch_regions(); + void set_disabled_branch_color(const Color &p_color); +}; + +class ShaderTextEditor : public CodeTextEditor { + GDCLASS(ShaderTextEditor, CodeTextEditor); + + Color marked_line_color = Color(1, 1, 1); + + struct WarningsComparator { + _ALWAYS_INLINE_ bool operator()(const ShaderWarning &p_a, const ShaderWarning &p_b) const { return (p_a.get_line() < p_b.get_line()); } + }; + + Ref<GDShaderSyntaxHighlighter> syntax_highlighter; + RichTextLabel *warnings_panel = nullptr; + Ref<Shader> shader; + Ref<ShaderInclude> shader_inc; + List<ShaderWarning> warnings; + Error last_compile_result = Error::OK; + + void _check_shader_mode(); + void _update_warning_panel(); + + bool block_shader_changed = false; + void _shader_changed(); + + uint32_t dependencies_version = 0; // Incremented if deps changed + +protected: + void _notification(int p_what); + static void _bind_methods(); + virtual void _load_theme_settings() override; + + virtual void _code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) override; + +public: + void set_block_shader_changed(bool p_block) { block_shader_changed = p_block; } + uint32_t get_dependencies_version() const { return dependencies_version; } + + virtual void _validate_script() override; + + void reload_text(); + void set_warnings_panel(RichTextLabel *p_warnings_panel); + + Ref<Shader> get_edited_shader() const; + Ref<ShaderInclude> get_edited_shader_include() const; + + void set_edited_shader(const Ref<Shader> &p_shader); + void set_edited_shader(const Ref<Shader> &p_shader, const String &p_code); + void set_edited_shader_include(const Ref<ShaderInclude> &p_include); + void set_edited_shader_include(const Ref<ShaderInclude> &p_include, const String &p_code); + void set_edited_code(const String &p_code); + + ShaderTextEditor(); +}; + +class TextShaderEditor : public MarginContainer { + GDCLASS(TextShaderEditor, MarginContainer); + + enum { + EDIT_UNDO, + EDIT_REDO, + EDIT_CUT, + EDIT_COPY, + EDIT_PASTE, + EDIT_SELECT_ALL, + EDIT_MOVE_LINE_UP, + EDIT_MOVE_LINE_DOWN, + EDIT_INDENT, + EDIT_UNINDENT, + EDIT_DELETE_LINE, + EDIT_DUPLICATE_SELECTION, + EDIT_TOGGLE_COMMENT, + EDIT_COMPLETE, + SEARCH_FIND, + SEARCH_FIND_NEXT, + SEARCH_FIND_PREV, + SEARCH_REPLACE, + SEARCH_GOTO_LINE, + BOOKMARK_TOGGLE, + BOOKMARK_GOTO_NEXT, + BOOKMARK_GOTO_PREV, + BOOKMARK_REMOVE_ALL, + HELP_DOCS, + }; + + MenuButton *edit_menu = nullptr; + MenuButton *search_menu = nullptr; + PopupMenu *bookmarks_menu = nullptr; + MenuButton *help_menu = nullptr; + PopupMenu *context_menu = nullptr; + RichTextLabel *warnings_panel = nullptr; + uint64_t idle = 0; + + GotoLineDialog *goto_line_dialog = nullptr; + ConfirmationDialog *erase_tab_confirm = nullptr; + ConfirmationDialog *disk_changed = nullptr; + + ShaderTextEditor *shader_editor = nullptr; + bool compilation_success = true; + + void _menu_option(int p_option); + mutable Ref<Shader> shader; + mutable Ref<ShaderInclude> shader_inc; + + void _editor_settings_changed(); + void _project_settings_changed(); + + void _check_for_external_edit(); + void _reload_shader_from_disk(); + void _reload_shader_include_from_disk(); + void _reload(); + void _show_warnings_panel(bool p_show); + void _warning_clicked(Variant p_line); + void _update_warnings(bool p_validate); + + void _script_validated(bool p_valid) { + compilation_success = p_valid; + emit_signal(SNAME("validation_changed")); + } + + uint32_t dependencies_version = 0xFFFFFFFF; + +protected: + void _notification(int p_what); + static void _bind_methods(); + void _make_context_menu(bool p_selection, Vector2 p_position); + void _text_edit_gui_input(const Ref<InputEvent> &p_ev); + + void _update_bookmark_list(); + void _bookmark_item_pressed(int p_idx); + +public: + bool was_compilation_successful() const { return compilation_success; } + void apply_shaders(); + void ensure_select_current(); + void edit(const Ref<Shader> &p_shader); + void edit(const Ref<ShaderInclude> &p_shader_inc); + void goto_line_selection(int p_line, int p_begin, int p_end); + void save_external_data(const String &p_str = ""); + void validate_script(); + bool is_unsaved() const; + void tag_saved_version(); + + virtual Size2 get_minimum_size() const override { return Size2(0, 200); } + + TextShaderEditor(); +}; + +#endif // TEXT_SHADER_EDITOR_H diff --git a/editor/plugins/texture_3d_editor_plugin.cpp b/editor/plugins/texture_3d_editor_plugin.cpp index 6080f9df87..9904b888f2 100644 --- a/editor/plugins/texture_3d_editor_plugin.cpp +++ b/editor/plugins/texture_3d_editor_plugin.cpp @@ -1,56 +1,53 @@ -/*************************************************************************/ -/* texture_3d_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* texture_3d_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 "texture_3d_editor_plugin.h" -#include "core/config/project_settings.h" -#include "core/io/resource_loader.h" -#include "editor/editor_settings.h" +#include "scene/gui/label.h" void Texture3DEditor::_texture_rect_draw() { texture_rect->draw_rect(Rect2(Point2(), texture_rect->get_size()), Color(1, 1, 1, 1)); } void Texture3DEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - //get_scene()->connect("node_removed",this,"_node_removed"); - } - if (p_what == NOTIFICATION_RESIZED) { - _texture_rect_update_area(); - } + switch (p_what) { + case NOTIFICATION_RESIZED: { + _texture_rect_update_area(); + } break; - if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); - Size2 size = get_size(); + case NOTIFICATION_DRAW: { + Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); + Size2 size = get_size(); - draw_texture_rect(checkerboard, Rect2(Point2(), size), true); + draw_texture_rect(checkerboard, Rect2(Point2(), size), true); + } break; } } @@ -58,12 +55,12 @@ void Texture3DEditor::_texture_changed() { if (!is_visible()) { return; } - update(); + queue_redraw(); } void Texture3DEditor::_update_material() { - material->set_shader_param("layer", (layer->get_value() + 0.5) / texture->get_depth()); - material->set_shader_param("tex", texture->get_rid()); + material->set_shader_parameter("layer", (layer->get_value() + 0.5) / texture->get_depth()); + material->set_shader_parameter("tex", texture->get_rid()); String format = Image::get_format_name(texture->get_format()); @@ -129,7 +126,7 @@ void Texture3DEditor::edit(Ref<Texture3D> p_texture) { } texture->connect("changed", callable_mp(this, &Texture3DEditor::_texture_changed)); - update(); + queue_redraw(); texture_rect->set_material(material); setting = true; layer->set_max(texture->get_depth() - 1); @@ -143,10 +140,6 @@ void Texture3DEditor::edit(Ref<Texture3D> p_texture) { } } -void Texture3DEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_layer_changed"), &Texture3DEditor::_layer_changed); -} - Texture3DEditor::Texture3DEditor() { set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED); set_custom_minimum_size(Size2(1, 150)); @@ -178,7 +171,7 @@ Texture3DEditor::Texture3DEditor() { info->add_theme_constant_override("shadow_offset_y", 2); setting = false; - layer->connect("value_changed", Callable(this, "_layer_changed")); + layer->connect("value_changed", callable_mp(this, &Texture3DEditor::_layer_changed)); } Texture3DEditor::~Texture3DEditor() { @@ -204,7 +197,7 @@ void EditorInspectorPlugin3DTexture::parse_begin(Object *p_object) { add_custom_control(editor); } -Texture3DEditorPlugin::Texture3DEditorPlugin(EditorNode *p_node) { +Texture3DEditorPlugin::Texture3DEditorPlugin() { Ref<EditorInspectorPlugin3DTexture> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/texture_3d_editor_plugin.h b/editor/plugins/texture_3d_editor_plugin.h index 5a200f6c11..bec3305289 100644 --- a/editor/plugins/texture_3d_editor_plugin.h +++ b/editor/plugins/texture_3d_editor_plugin.h @@ -1,52 +1,53 @@ -/*************************************************************************/ -/* texture_3d_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* texture_3d_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 TEXTURE_3D_EDITOR_PLUGIN_H #define TEXTURE_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/editor_inspector.h" #include "editor/editor_plugin.h" +#include "scene/gui/spin_box.h" #include "scene/resources/shader.h" #include "scene/resources/texture.h" class Texture3DEditor : public Control { GDCLASS(Texture3DEditor, Control); - SpinBox *layer; - Label *info; + SpinBox *layer = nullptr; + Label *info = nullptr; Ref<Texture3D> texture; Ref<Shader> shader; Ref<ShaderMaterial> material; - Control *texture_rect; + Control *texture_rect = nullptr; void _make_shaders(); @@ -66,8 +67,6 @@ class Texture3DEditor : public Control { protected: void _notification(int p_what); - static void _bind_methods(); - public: void edit(Ref<Texture3D> p_texture); Texture3DEditor(); @@ -88,7 +87,7 @@ class Texture3DEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "Texture3D"; } - Texture3DEditorPlugin(EditorNode *p_node); + Texture3DEditorPlugin(); }; -#endif // TEXTURE_EDITOR_PLUGIN_H +#endif // TEXTURE_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index a07cc299f2..2fb624b713 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -1,36 +1,37 @@ -/*************************************************************************/ -/* texture_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* texture_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 "texture_editor_plugin.h" - #include "editor/editor_scale.h" +#include "scene/gui/label.h" +#include "scene/gui/texture_rect.h" TextureRect *TexturePreview::get_texture_display() { return texture_display; @@ -59,18 +60,60 @@ void TexturePreview::_notification(int p_what) { } void TexturePreview::_update_metadata_label_text() { - Ref<Texture2D> texture = texture_display->get_texture(); + const Ref<Texture2D> texture = texture_display->get_texture(); String format; if (Object::cast_to<ImageTexture>(*texture)) { format = Image::get_format_name(Object::cast_to<ImageTexture>(*texture)->get_format()); - } else if (Object::cast_to<StreamTexture2D>(*texture)) { - format = Image::get_format_name(Object::cast_to<StreamTexture2D>(*texture)->get_format()); + } else if (Object::cast_to<CompressedTexture2D>(*texture)) { + format = Image::get_format_name(Object::cast_to<CompressedTexture2D>(*texture)->get_format()); } else { format = texture->get_class(); } - metadata_label->set_text(itos(texture->get_width()) + "x" + itos(texture->get_height()) + " " + format); + const Ref<Image> image = texture->get_image(); + if (image.is_valid()) { + const int mipmaps = image->get_mipmap_count(); + // Avoid signed integer overflow that could occur with huge texture sizes by casting everything to uint64_t. + uint64_t memory = uint64_t(image->get_width()) * uint64_t(image->get_height()) * uint64_t(Image::get_format_pixel_size(image->get_format())); + // Handle VRAM-compressed formats that are stored with 4 bpp. + memory >>= Image::get_format_pixel_rshift(image->get_format()); + + float mipmaps_multiplier = 1.0; + float mipmap_increase = 0.25; + for (int i = 0; i < mipmaps; i++) { + // Each mip adds 25% memory usage of the previous one. + // With a complete mipmap chain, memory usage increases by ~33%. + mipmaps_multiplier += mipmap_increase; + mipmap_increase *= 0.25; + } + memory *= mipmaps_multiplier; + + if (mipmaps >= 1) { + metadata_label->set_text( + vformat(String::utf8("%d×%d %s\n") + TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"), + texture->get_width(), + texture->get_height(), + format, + mipmaps, + String::humanize_size(memory))); + } else { + // "No Mipmaps" is easier to distinguish than "0 Mipmaps", + // especially since 0, 6, and 8 look quite close with the default code font. + metadata_label->set_text( + vformat(String::utf8("%d×%d %s\n") + TTR("No Mipmaps") + "\n" + TTR("Memory: %s"), + texture->get_width(), + texture->get_height(), + format, + String::humanize_size(memory))); + } + } else { + metadata_label->set_text( + vformat(String::utf8("%d×%d %s"), + texture->get_width(), + texture->get_height(), + format)); + } } TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { @@ -81,10 +124,11 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { add_child(checkerboard); texture_display = memnew(TextureRect); + texture_display->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS); texture_display->set_texture(p_texture); - texture_display->set_anchors_preset(TextureRect::PRESET_WIDE); + texture_display->set_anchors_preset(TextureRect::PRESET_FULL_RECT); texture_display->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); - texture_display->set_expand(true); + texture_display->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); add_child(texture_display); if (p_show_metadata) { @@ -95,13 +139,11 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { // It's okay that these colors are static since the grid color is static too. metadata_label->add_theme_color_override("font_color", Color::named("white")); - metadata_label->add_theme_color_override("font_color_shadow", Color::named("black")); + metadata_label->add_theme_color_override("font_shadow_color", Color::named("black")); - metadata_label->add_theme_font_size_override("font_size", 16 * EDSCALE); + metadata_label->add_theme_font_size_override("font_size", 14 * EDSCALE); metadata_label->add_theme_color_override("font_outline_color", Color::named("black")); - metadata_label->add_theme_constant_override("outline_size", 2 * EDSCALE); - - metadata_label->add_theme_constant_override("shadow_outline_size", 1); + metadata_label->add_theme_constant_override("outline_size", 8 * EDSCALE); metadata_label->set_h_size_flags(Control::SIZE_SHRINK_END); metadata_label->set_v_size_flags(Control::SIZE_SHRINK_END); @@ -110,7 +152,7 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { } bool EditorInspectorPluginTexture::can_handle(Object *p_object) { - return Object::cast_to<ImageTexture>(p_object) != nullptr || Object::cast_to<AtlasTexture>(p_object) != nullptr || Object::cast_to<StreamTexture2D>(p_object) != nullptr || Object::cast_to<AnimatedTexture>(p_object) != nullptr; + return Object::cast_to<ImageTexture>(p_object) != nullptr || Object::cast_to<AtlasTexture>(p_object) != nullptr || Object::cast_to<CompressedTexture2D>(p_object) != nullptr || Object::cast_to<AnimatedTexture>(p_object) != nullptr; } void EditorInspectorPluginTexture::parse_begin(Object *p_object) { @@ -119,7 +161,7 @@ void EditorInspectorPluginTexture::parse_begin(Object *p_object) { add_custom_control(memnew(TexturePreview(texture, true))); } -TextureEditorPlugin::TextureEditorPlugin(EditorNode *p_node) { +TextureEditorPlugin::TextureEditorPlugin() { Ref<EditorInspectorPluginTexture> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/texture_editor_plugin.h b/editor/plugins/texture_editor_plugin.h index 5ba077d6fc..f045e7b1b0 100644 --- a/editor/plugins/texture_editor_plugin.h +++ b/editor/plugins/texture_editor_plugin.h @@ -1,40 +1,43 @@ -/*************************************************************************/ -/* texture_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* texture_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 TEXTURE_EDITOR_PLUGIN_H #define TEXTURE_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/editor_inspector.h" #include "editor/editor_plugin.h" +#include "scene/gui/margin_container.h" #include "scene/resources/texture.h" +class TextureRect; + class TexturePreview : public MarginContainer { GDCLASS(TexturePreview, MarginContainer); @@ -68,7 +71,7 @@ class TextureEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "Texture2D"; } - TextureEditorPlugin(EditorNode *p_node); + TextureEditorPlugin(); }; #endif // TEXTURE_EDITOR_PLUGIN_H diff --git a/editor/plugins/texture_layered_editor_plugin.cpp b/editor/plugins/texture_layered_editor_plugin.cpp index a8c37d37fe..816d081617 100644 --- a/editor/plugins/texture_layered_editor_plugin.cpp +++ b/editor/plugins/texture_layered_editor_plugin.cpp @@ -1,44 +1,42 @@ -/*************************************************************************/ -/* texture_layered_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* texture_layered_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 "texture_layered_editor_plugin.h" -#include "core/config/project_settings.h" -#include "core/io/resource_loader.h" -#include "editor/editor_settings.h" +#include "scene/gui/label.h" void TextureLayeredEditor::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { + if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { y_rot += -mm->get_relative().x * 0.01; x_rot += mm->get_relative().y * 0.01; _update_material(); @@ -50,18 +48,17 @@ void TextureLayeredEditor::_texture_rect_draw() { } void TextureLayeredEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - //get_scene()->connect("node_removed",this,"_node_removed"); - } - if (p_what == NOTIFICATION_RESIZED) { - _texture_rect_update_area(); - } + switch (p_what) { + case NOTIFICATION_RESIZED: { + _texture_rect_update_area(); + } break; - if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); - Size2 size = get_size(); + case NOTIFICATION_DRAW: { + Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); + Size2 size = get_size(); - draw_texture_rect(checkerboard, Rect2(Point2(), size), true); + draw_texture_rect(checkerboard, Rect2(Point2(), size), true); + } break; } } @@ -69,13 +66,13 @@ void TextureLayeredEditor::_texture_changed() { if (!is_visible()) { return; } - update(); + queue_redraw(); } void TextureLayeredEditor::_update_material() { - materials[0]->set_shader_param("layer", layer->get_value()); - materials[2]->set_shader_param("layer", layer->get_value()); - materials[texture->get_layered_type()]->set_shader_param("tex", texture->get_rid()); + materials[0]->set_shader_parameter("layer", layer->get_value()); + materials[2]->set_shader_parameter("layer", layer->get_value()); + materials[texture->get_layered_type()]->set_shader_parameter("tex", texture->get_rid()); Vector3 v(1, 1, 1); v.normalize(); @@ -84,10 +81,10 @@ void TextureLayeredEditor::_update_material() { b.rotate(Vector3(1, 0, 0), x_rot); b.rotate(Vector3(0, 1, 0), y_rot); - materials[1]->set_shader_param("normal", v); - materials[1]->set_shader_param("rot", b); - materials[2]->set_shader_param("normal", v); - materials[2]->set_shader_param("rot", b); + materials[1]->set_shader_parameter("normal", v); + materials[1]->set_shader_parameter("rot", b); + materials[2]->set_shader_parameter("normal", v); + materials[2]->set_shader_parameter("rot", b); String format = Image::get_format_name(texture->get_format()); @@ -195,7 +192,7 @@ void TextureLayeredEditor::edit(Ref<TextureLayered> p_texture) { } texture->connect("changed", callable_mp(this, &TextureLayeredEditor::_texture_changed)); - update(); + queue_redraw(); texture_rect->set_material(materials[texture->get_layered_type()]); setting = true; if (texture->get_layered_type() == TextureLayered::LAYERED_TYPE_2D_ARRAY) { @@ -219,10 +216,6 @@ void TextureLayeredEditor::edit(Ref<TextureLayered> p_texture) { } } -void TextureLayeredEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_layer_changed"), &TextureLayeredEditor::_layer_changed); -} - TextureLayeredEditor::TextureLayeredEditor() { set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED); set_custom_minimum_size(Size2(1, 150)); @@ -254,7 +247,7 @@ TextureLayeredEditor::TextureLayeredEditor() { info->add_theme_constant_override("shadow_offset_y", 2); setting = false; - layer->connect("value_changed", Callable(this, "_layer_changed")); + layer->connect("value_changed", callable_mp(this, &TextureLayeredEditor::_layer_changed)); } TextureLayeredEditor::~TextureLayeredEditor() { @@ -277,7 +270,7 @@ void EditorInspectorPluginLayeredTexture::parse_begin(Object *p_object) { add_custom_control(editor); } -TextureLayeredEditorPlugin::TextureLayeredEditorPlugin(EditorNode *p_node) { +TextureLayeredEditorPlugin::TextureLayeredEditorPlugin() { Ref<EditorInspectorPluginLayeredTexture> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/texture_layered_editor_plugin.h b/editor/plugins/texture_layered_editor_plugin.h index cd8eba1bfe..39cbb32d16 100644 --- a/editor/plugins/texture_layered_editor_plugin.h +++ b/editor/plugins/texture_layered_editor_plugin.h @@ -1,46 +1,47 @@ -/*************************************************************************/ -/* texture_layered_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* texture_layered_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 TEXTURE_LAYERED_EDITOR_PLUGIN_H #define TEXTURE_LAYERED_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/editor_inspector.h" #include "editor/editor_plugin.h" +#include "scene/gui/spin_box.h" #include "scene/resources/shader.h" #include "scene/resources/texture.h" class TextureLayeredEditor : public Control { GDCLASS(TextureLayeredEditor, Control); - SpinBox *layer; - Label *info; + SpinBox *layer = nullptr; + Label *info = nullptr; Ref<TextureLayered> texture; Ref<Shader> shaders[3]; @@ -48,7 +49,7 @@ class TextureLayeredEditor : public Control { float x_rot = 0; float y_rot = 0; - Control *texture_rect; + Control *texture_rect = nullptr; void _make_shaders(); @@ -68,7 +69,6 @@ class TextureLayeredEditor : public Control { protected: void _notification(int p_what); virtual void gui_input(const Ref<InputEvent> &p_event) override; - static void _bind_methods(); public: void edit(Ref<TextureLayered> p_texture); @@ -90,7 +90,7 @@ class TextureLayeredEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "TextureLayered"; } - TextureLayeredEditorPlugin(EditorNode *p_node); + TextureLayeredEditorPlugin(); }; -#endif // TEXTURE_EDITOR_PLUGIN_H +#endif // TEXTURE_LAYERED_EDITOR_PLUGIN_H diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index c03e55be69..7fa16e6cc6 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -1,44 +1,48 @@ -/*************************************************************************/ -/* texture_region_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* texture_region_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 "texture_region_editor_plugin.h" #include "core/core_string_names.h" #include "core/input/input.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "scene/gui/check_box.h" - -/** - @author Mariano Suligoy -*/ +#include "scene/gui/option_button.h" +#include "scene/gui/separator.h" +#include "scene/gui/spin_box.h" +#include "scene/gui/view_panner.h" +#include "scene/resources/texture.h" void draw_margin_line(Control *edit_draw, Vector2 from, Vector2 to) { Vector2 line = (to - from).normalized() * 10; @@ -80,15 +84,18 @@ void TextureRegionEditor::_region_draw() { } Transform2D mtx; - mtx.elements[2] = -draw_ofs * draw_zoom; + mtx.columns[2] = -draw_ofs * draw_zoom; mtx.scale_basis(Vector2(draw_zoom, draw_zoom)); RS::get_singleton()->canvas_item_add_set_transform(edit_draw->get_canvas_item(), mtx); - edit_draw->draw_texture(base_tex, Point2()); + edit_draw->draw_rect(Rect2(Point2(), preview_tex->get_size()), Color(0.5, 0.5, 0.5, 0.5), false); + edit_draw->draw_texture(preview_tex, Point2()); RS::get_singleton()->canvas_item_add_set_transform(edit_draw->get_canvas_item(), Transform2D()); + const Color color = get_theme_color(SNAME("mono_color"), SNAME("Editor")); + if (snap_mode == SNAP_GRID) { - Color grid_color = Color(1.0, 1.0, 1.0, 0.15); + const Color grid_color = Color(color.r, color.g, color.b, color.a * 0.15); Size2 s = edit_draw->get_size(); int last_cell = 0; @@ -145,7 +152,7 @@ void TextureRegionEditor::_region_draw() { } } else if (snap_mode == SNAP_AUTOSLICE) { for (const Rect2 &r : autoslice_cache) { - Vector2 endpoints[4] = { + const Vector2 endpoints[4] = { mtx.basis_xform(r.position), mtx.basis_xform(r.position + Vector2(r.size.x, 0)), mtx.basis_xform(r.position + r.size), @@ -174,7 +181,6 @@ void TextureRegionEditor::_region_draw() { mtx.basis_xform(raw_endpoints[2]), mtx.basis_xform(raw_endpoints[3]) }; - Color color = get_theme_color(SNAME("mono_color"), SNAME("Editor")); for (int i = 0; i < 4; i++) { int prev = (i + 3) % 4; int next = (i + 1) % 4; @@ -229,11 +235,19 @@ void TextureRegionEditor::_region_draw() { Size2 vmin = vscroll->get_combined_minimum_size(); // Avoid scrollbar overlapping. - hscroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, vscroll->is_visible() ? -vmin.width : 0); - vscroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, hscroll->is_visible() ? -hmin.height : 0); + hscroll->set_anchor_and_offset(SIDE_RIGHT, Control::ANCHOR_END, vscroll->is_visible() ? -vmin.width : 0); + vscroll->set_anchor_and_offset(SIDE_BOTTOM, Control::ANCHOR_END, hscroll->is_visible() ? -hmin.height : 0); updating_scroll = false; + if (request_center && hscroll->get_min() < 0) { + hscroll->set_value((hscroll->get_min() + hscroll->get_max() - hscroll->get_page()) / 2); + vscroll->set_value((vscroll->get_min() + vscroll->get_max() - vscroll->get_page()) / 2); + // This ensures that the view is updated correctly. + callable_mp(this, &TextureRegionEditor::_pan_callback).bind(Vector2(1, 0)).call_deferred(); + request_center = false; + } + if (node_ninepatch || obj_styleBox.is_valid()) { float margins[4] = { 0 }; if (node_ninepatch) { @@ -242,10 +256,10 @@ void TextureRegionEditor::_region_draw() { margins[2] = node_ninepatch->get_patch_margin(SIDE_LEFT); margins[3] = node_ninepatch->get_patch_margin(SIDE_RIGHT); } else if (obj_styleBox.is_valid()) { - margins[0] = obj_styleBox->get_margin_size(SIDE_TOP); - margins[1] = obj_styleBox->get_margin_size(SIDE_BOTTOM); - margins[2] = obj_styleBox->get_margin_size(SIDE_LEFT); - margins[3] = obj_styleBox->get_margin_size(SIDE_RIGHT); + margins[0] = obj_styleBox->get_texture_margin(SIDE_TOP); + margins[1] = obj_styleBox->get_texture_margin(SIDE_BOTTOM); + margins[2] = obj_styleBox->get_texture_margin(SIDE_LEFT); + margins[3] = obj_styleBox->get_texture_margin(SIDE_RIGHT); } Vector2 pos[4] = { @@ -263,8 +277,12 @@ void TextureRegionEditor::_region_draw() { } void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { + if (panner->gui_input(p_input)) { + return; + } + Transform2D mtx; - mtx.elements[2] = -draw_ofs * draw_zoom; + mtx.columns[2] = -draw_ofs * draw_zoom; mtx.scale_basis(Vector2(draw_zoom, draw_zoom)); const real_t handle_radius = 8 * EDSCALE; @@ -282,10 +300,11 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { mtx.xform(rect.position + Vector2(0, rect.size.y / 2)) + Vector2(-handle_offset, 0) }; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); Ref<InputEventMouseButton> mb = p_input; if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::LEFT) { - if (mb->is_pressed()) { + if (mb->is_pressed() && !panner->is_panning()) { if (node_ninepatch || obj_styleBox.is_valid()) { edited_margin = -1; float margins[4] = { 0 }; @@ -295,10 +314,10 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { margins[2] = node_ninepatch->get_patch_margin(SIDE_LEFT); margins[3] = node_ninepatch->get_patch_margin(SIDE_RIGHT); } else if (obj_styleBox.is_valid()) { - margins[0] = obj_styleBox->get_margin_size(SIDE_TOP); - margins[1] = obj_styleBox->get_margin_size(SIDE_BOTTOM); - margins[2] = obj_styleBox->get_margin_size(SIDE_LEFT); - margins[3] = obj_styleBox->get_margin_size(SIDE_RIGHT); + margins[0] = obj_styleBox->get_texture_margin(SIDE_TOP); + margins[1] = obj_styleBox->get_texture_margin(SIDE_BOTTOM); + margins[2] = obj_styleBox->get_texture_margin(SIDE_LEFT); + margins[3] = obj_styleBox->get_texture_margin(SIDE_RIGHT); } Vector2 pos[4] = { @@ -330,7 +349,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { for (const Rect2 &E : autoslice_cache) { if (E.has_point(point)) { rect = E; - if (Input::get_singleton()->is_key_pressed(Key::CTRL) && !(Input::get_singleton()->is_key_pressed(Key(Key::SHIFT | Key::ALT)))) { + if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL) && !(Input::get_singleton()->is_key_pressed(Key(Key::SHIFT | Key::ALT)))) { Rect2 r; if (atlas_tex.is_valid()) { r = atlas_tex->get_region(); @@ -365,8 +384,8 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { } undo_redo->add_do_method(this, "_update_rect"); undo_redo->add_undo_method(this, "_update_rect"); - undo_redo->add_do_method(edit_draw, "update"); - undo_redo->add_undo_method(edit_draw, "update"); + undo_redo->add_do_method(edit_draw, "queue_redraw"); + undo_redo->add_undo_method(edit_draw, "queue_redraw"); undo_redo->commit_action(); break; } @@ -404,7 +423,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { } } - } else if (drag) { + } else if (!mb->is_pressed() && drag) { if (edited_margin >= 0) { undo_redo->create_action(TTR("Set Margin")); static Side side[4] = { SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT }; @@ -412,8 +431,8 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { undo_redo->add_do_method(node_ninepatch, "set_patch_margin", side[edited_margin], node_ninepatch->get_patch_margin(side[edited_margin])); undo_redo->add_undo_method(node_ninepatch, "set_patch_margin", side[edited_margin], prev_margin); } else if (obj_styleBox.is_valid()) { - undo_redo->add_do_method(obj_styleBox.ptr(), "set_margin_size", side[edited_margin], obj_styleBox->get_margin_size(side[edited_margin])); - undo_redo->add_undo_method(obj_styleBox.ptr(), "set_margin_size", side[edited_margin], prev_margin); + undo_redo->add_do_method(obj_styleBox.ptr(), "set_texture_margin", side[edited_margin], obj_styleBox->get_texture_margin(side[edited_margin])); + undo_redo->add_undo_method(obj_styleBox.ptr(), "set_texture_margin", side[edited_margin], prev_margin); obj_styleBox->emit_signal(CoreStringNames::get_singleton()->changed); } edited_margin = -1; @@ -439,8 +458,8 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { } undo_redo->add_do_method(this, "_update_rect"); undo_redo->add_undo_method(this, "_update_rect"); - undo_redo->add_do_method(edit_draw, "update"); - undo_redo->add_undo_method(edit_draw, "update"); + undo_redo->add_do_method(edit_draw, "queue_redraw"); + undo_redo->add_undo_method(edit_draw, "queue_redraw"); undo_redo->commit_action(); drag = false; creating = false; @@ -455,31 +474,23 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { node_ninepatch->set_patch_margin(side[edited_margin], prev_margin); } if (obj_styleBox.is_valid()) { - obj_styleBox->set_margin_size(side[edited_margin], prev_margin); + obj_styleBox->set_texture_margin(side[edited_margin], prev_margin); } edited_margin = -1; } else { apply_rect(rect_prev); rect = rect_prev; - edit_draw->update(); + edit_draw->queue_redraw(); drag_index = -1; } } - } else if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) { - _zoom_on_position(draw_zoom * ((0.95 + (0.05 * mb->get_factor())) / 0.95), mb->get_position()); - } else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) { - _zoom_on_position(draw_zoom * (1 - (0.05 * mb->get_factor())), mb->get_position()); } } Ref<InputEventMouseMotion> mm = p_input; if (mm.is_valid()) { - if ((mm->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE || Input::get_singleton()->is_key_pressed(Key::SPACE)) { - Vector2 dragged(mm->get_relative().x / draw_zoom, mm->get_relative().y / draw_zoom); - hscroll->set_value(hscroll->get_value() - dragged.x); - vscroll->set_value(vscroll->get_value() - dragged.y); - } else if (drag) { + if (drag) { if (edited_margin >= 0) { float new_margin = 0; @@ -524,7 +535,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { node_ninepatch->set_patch_margin(side[edited_margin], new_margin); } if (obj_styleBox.is_valid()) { - obj_styleBox->set_margin_size(side[edited_margin], new_margin); + obj_styleBox->set_texture_margin(side[edited_margin], new_margin); } } else { Vector2 new_pos = mtx.affine_inverse().xform(mm->get_position()); @@ -538,7 +549,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { rect = Rect2(drag_from, Size2()); rect.expand_to(new_pos); apply_rect(rect); - edit_draw->update(); + edit_draw->queue_redraw(); return; } @@ -593,7 +604,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { } break; } } - edit_draw->update(); + edit_draw->queue_redraw(); } } @@ -609,6 +620,16 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { } } +void TextureRegionEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) { + p_scroll_vec /= draw_zoom; + hscroll->set_value(hscroll->get_value() - p_scroll_vec.x); + vscroll->set_value(vscroll->get_value() - p_scroll_vec.y); +} + +void TextureRegionEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) { + _zoom_on_position(draw_zoom * p_zoom_factor, p_origin); +} + void TextureRegionEditor::_scroll_changed(float) { if (updating_scroll) { return; @@ -616,7 +637,7 @@ void TextureRegionEditor::_scroll_changed(float) { draw_ofs.x = hscroll->get_value(); draw_ofs.y = vscroll->get_value(); - edit_draw->update(); + edit_draw->queue_redraw(); } void TextureRegionEditor::_set_snap_mode(int p_mode) { @@ -632,37 +653,37 @@ void TextureRegionEditor::_set_snap_mode(int p_mode) { _update_autoslice(); } - edit_draw->update(); + edit_draw->queue_redraw(); } void TextureRegionEditor::_set_snap_off_x(float p_val) { snap_offset.x = p_val; - edit_draw->update(); + edit_draw->queue_redraw(); } void TextureRegionEditor::_set_snap_off_y(float p_val) { snap_offset.y = p_val; - edit_draw->update(); + edit_draw->queue_redraw(); } void TextureRegionEditor::_set_snap_step_x(float p_val) { snap_step.x = p_val; - edit_draw->update(); + edit_draw->queue_redraw(); } void TextureRegionEditor::_set_snap_step_y(float p_val) { snap_step.y = p_val; - edit_draw->update(); + edit_draw->queue_redraw(); } void TextureRegionEditor::_set_snap_sep_x(float p_val) { snap_separation.x = p_val; - edit_draw->update(); + edit_draw->queue_redraw(); } void TextureRegionEditor::_set_snap_sep_y(float p_val) { snap_separation.y = p_val; - edit_draw->update(); + edit_draw->queue_redraw(); } void TextureRegionEditor::_zoom_on_position(float p_zoom, Point2 p_position) { @@ -676,7 +697,7 @@ void TextureRegionEditor::_zoom_on_position(float p_zoom, Point2 p_position) { ofs = ofs / prev_zoom - ofs / draw_zoom; draw_ofs = (draw_ofs + ofs).round(); - edit_draw->update(); + edit_draw->queue_redraw(); } void TextureRegionEditor::_zoom_in() { @@ -719,6 +740,9 @@ void TextureRegionEditor::_update_rect() { } } else if (obj_styleBox.is_valid()) { rect = obj_styleBox->get_region_rect(); + if (rect == Rect2()) { + rect = Rect2(Vector2(), obj_styleBox->get_texture()->get_size()); + } } } @@ -795,23 +819,37 @@ void TextureRegionEditor::_update_autoslice() { void TextureRegionEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_EXIT_TREE: { + get_tree()->disconnect("node_removed", callable_mp(this, &TextureRegionEditor::_node_removed)); + } break; + + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", callable_mp(this, &TextureRegionEditor::_node_removed)); + [[fallthrough]]; + } case NOTIFICATION_THEME_CHANGED: { - edit_draw->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + edit_draw->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); } break; + case NOTIFICATION_READY: { zoom_out->set_icon(get_theme_icon(SNAME("ZoomLess"), SNAME("EditorIcons"))); zoom_reset->set_icon(get_theme_icon(SNAME("ZoomReset"), SNAME("EditorIcons"))); zoom_in->set_icon(get_theme_icon(SNAME("ZoomMore"), SNAME("EditorIcons"))); - vscroll->set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE); - hscroll->set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); + vscroll->set_anchors_and_offsets_preset(Control::PRESET_RIGHT_WIDE); + hscroll->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_WIDE); + [[fallthrough]]; + } + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (snap_mode == SNAP_AUTOSLICE && is_visible() && autoslice_is_dirty) { _update_autoslice(); } } break; + case NOTIFICATION_WM_WINDOW_FOCUS_IN: { // This happens when the user leaves the Editor and returns, // they could have changed the textures, so the cache is cleared. @@ -834,7 +872,6 @@ void TextureRegionEditor::_node_removed(Object *p_obj) { void TextureRegionEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_edit_region"), &TextureRegionEditor::_edit_region); - ClassDB::bind_method(D_METHOD("_node_removed"), &TextureRegionEditor::_node_removed); ClassDB::bind_method(D_METHOD("_zoom_on_position"), &TextureRegionEditor::_zoom_on_position); ClassDB::bind_method(D_METHOD("_update_rect"), &TextureRegionEditor::_update_rect); } @@ -875,6 +912,13 @@ void TextureRegionEditor::edit(Object *p_obj) { if (atlas_tex.is_valid()) { atlas_tex->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); } + + node_sprite_2d = nullptr; + node_sprite_3d = nullptr; + node_ninepatch = nullptr; + obj_styleBox = Ref<StyleBoxTexture>(nullptr); + atlas_tex = Ref<AtlasTexture>(nullptr); + if (p_obj) { node_sprite_2d = Object::cast_to<Sprite2D>(p_obj); node_sprite_3d = Object::cast_to<Sprite3D>(p_obj); @@ -896,20 +940,11 @@ void TextureRegionEditor::edit(Object *p_obj) { p_obj->connect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); } _edit_region(); - } else { - node_sprite_2d = nullptr; - node_sprite_3d = nullptr; - node_ninepatch = nullptr; - obj_styleBox = Ref<StyleBoxTexture>(nullptr); - atlas_tex = Ref<AtlasTexture>(nullptr); - } - edit_draw->update(); - if ((node_sprite_2d && !node_sprite_2d->is_region_enabled()) || (node_sprite_3d && !node_sprite_3d->is_region_enabled())) { - set_process(true); - } - if (!p_obj) { - set_process(false); } + + edit_draw->queue_redraw(); + popup_centered_ratio(0.5); + request_center = true; } void TextureRegionEditor::_texture_changed() { @@ -920,27 +955,90 @@ void TextureRegionEditor::_texture_changed() { } void TextureRegionEditor::_edit_region() { + CanvasItem::TextureFilter filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS; + Ref<Texture2D> texture = nullptr; if (atlas_tex.is_valid()) { texture = atlas_tex->get_atlas(); } else if (node_sprite_2d) { texture = node_sprite_2d->get_texture(); + filter = node_sprite_2d->get_texture_filter_in_tree(); } else if (node_sprite_3d) { texture = node_sprite_3d->get_texture(); + + StandardMaterial3D::TextureFilter filter_3d = node_sprite_3d->get_texture_filter(); + + switch (filter_3d) { + case StandardMaterial3D::TEXTURE_FILTER_NEAREST: + filter = CanvasItem::TEXTURE_FILTER_NEAREST; + break; + case StandardMaterial3D::TEXTURE_FILTER_LINEAR: + filter = CanvasItem::TEXTURE_FILTER_LINEAR; + break; + case StandardMaterial3D::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS: + filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS; + break; + case StandardMaterial3D::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS: + filter = CanvasItem::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS; + break; + case StandardMaterial3D::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC: + filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC; + break; + case StandardMaterial3D::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC: + filter = CanvasItem::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC; + break; + default: + // fallback to project default + filter = CanvasItem::TEXTURE_FILTER_PARENT_NODE; + break; + } } else if (node_ninepatch) { texture = node_ninepatch->get_texture(); + filter = node_ninepatch->get_texture_filter_in_tree(); } else if (obj_styleBox.is_valid()) { texture = obj_styleBox->get_texture(); } + // occurs when get_texture_filter_in_tree reaches the scene root + if (filter == CanvasItem::TEXTURE_FILTER_PARENT_NODE) { + SubViewport *root = EditorNode::get_singleton()->get_scene_root(); + + if (root != nullptr) { + Viewport::DefaultCanvasItemTextureFilter filter_default = root->get_default_canvas_item_texture_filter(); + + // depending on default filter, set filter to match, otherwise fall back on nearest w/ mipmaps + switch (filter_default) { + case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST: + filter = CanvasItem::TEXTURE_FILTER_NEAREST; + break; + case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR: + filter = CanvasItem::TEXTURE_FILTER_LINEAR; + break; + case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS: + filter = CanvasItem::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS; + break; + case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS: + default: + filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS; + break; + } + } else { + filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS; + } + } + if (texture.is_null()) { + preview_tex->set_diffuse_texture(nullptr); _zoom_reset(); hscroll->hide(); vscroll->hide(); - edit_draw->update(); + edit_draw->queue_redraw(); return; } + preview_tex->set_texture_filter(filter); + preview_tex->set_diffuse_texture(texture); + if (cache_map.has(texture->get_rid())) { autoslice_cache = cache_map[texture->get_rid()]; autoslice_is_dirty = false; @@ -953,7 +1051,7 @@ void TextureRegionEditor::_edit_region() { } _update_rect(); - edit_draw->update(); + edit_draw->queue_redraw(); } Vector2 TextureRegionEditor::snap_point(Vector2 p_target) const { @@ -965,14 +1063,17 @@ Vector2 TextureRegionEditor::snap_point(Vector2 p_target) const { return p_target; } -TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) { +TextureRegionEditor::TextureRegionEditor() { + set_ok_button_text(TTR("Close")); + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); node_sprite_2d = nullptr; node_sprite_3d = nullptr; node_ninepatch = nullptr; obj_styleBox = Ref<StyleBoxTexture>(nullptr); atlas_tex = Ref<AtlasTexture>(nullptr); - editor = p_editor; - undo_redo = editor->get_undo_redo(); + + preview_tex = Ref<CanvasTexture>(memnew(CanvasTexture)); snap_step = Vector2(10, 10); snap_separation = Vector2(0, 0); @@ -982,7 +1083,7 @@ TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) { drag = false; HBoxContainer *hb_tools = memnew(HBoxContainer); - add_child(hb_tools); + vb->add_child(hb_tools); hb_tools->add_child(memnew(Label(TTR("Snap Mode:")))); snap_mode_button = memnew(OptionButton); @@ -1062,11 +1163,16 @@ TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) { hb_grid->hide(); + panner.instantiate(); + panner->set_callbacks(callable_mp(this, &TextureRegionEditor::_pan_callback), callable_mp(this, &TextureRegionEditor::_zoom_callback)); + edit_draw = memnew(Panel); - add_child(edit_draw); - edit_draw->set_v_size_flags(SIZE_EXPAND_FILL); + vb->add_child(edit_draw); + edit_draw->set_v_size_flags(Control::SIZE_EXPAND_FILL); edit_draw->connect("draw", callable_mp(this, &TextureRegionEditor::_region_draw)); edit_draw->connect("gui_input", callable_mp(this, &TextureRegionEditor::_region_input)); + edit_draw->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key)); + edit_draw->set_focus_mode(Control::FOCUS_CLICK); draw_zoom = 1.0; edit_draw->set_clip_contents(true); @@ -1077,19 +1183,19 @@ TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) { zoom_out = memnew(Button); zoom_out->set_flat(true); - zoom_out->set_tooltip(TTR("Zoom Out")); + zoom_out->set_tooltip_text(TTR("Zoom Out")); zoom_out->connect("pressed", callable_mp(this, &TextureRegionEditor::_zoom_out)); zoom_hb->add_child(zoom_out); zoom_reset = memnew(Button); zoom_reset->set_flat(true); - zoom_reset->set_tooltip(TTR("Zoom Reset")); + zoom_reset->set_tooltip_text(TTR("Zoom Reset")); zoom_reset->connect("pressed", callable_mp(this, &TextureRegionEditor::_zoom_reset)); zoom_hb->add_child(zoom_reset); zoom_in = memnew(Button); zoom_in->set_flat(true); - zoom_in->set_tooltip(TTR("Zoom In")); + zoom_in->set_tooltip_text(TTR("Zoom In")); zoom_in->connect("pressed", callable_mp(this, &TextureRegionEditor::_zoom_in)); zoom_hb->add_child(zoom_in); @@ -1104,89 +1210,39 @@ TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) { updating_scroll = false; autoslice_is_dirty = true; -} -void TextureRegionEditorPlugin::edit(Object *p_object) { - region_editor->edit(p_object); + set_title(TTR("Region Editor")); } -bool TextureRegionEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("Sprite2D") || p_object->is_class("Sprite3D") || p_object->is_class("NinePatchRect") || p_object->is_class("StyleBoxTexture") || p_object->is_class("AtlasTexture"); -} +//////////////////////// -void TextureRegionEditorPlugin::_editor_visiblity_changed() { - manually_hidden = !region_editor->is_visible_in_tree(); +bool EditorInspectorPluginTextureRegion::can_handle(Object *p_object) { + return Object::cast_to<Sprite2D>(p_object) || Object::cast_to<Sprite3D>(p_object) || Object::cast_to<NinePatchRect>(p_object) || Object::cast_to<StyleBoxTexture>(p_object) || Object::cast_to<AtlasTexture>(p_object); } -void TextureRegionEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - texture_region_button->show(); - bool is_node_configured = region_editor->is_stylebox() || region_editor->is_atlas_texture() || region_editor->is_ninepatch(); - is_node_configured |= region_editor->get_sprite_2d() && region_editor->get_sprite_2d()->is_region_enabled(); - is_node_configured |= region_editor->get_sprite_3d() && region_editor->get_sprite_3d()->is_region_enabled(); - if ((is_node_configured && !manually_hidden) || texture_region_button->is_pressed()) { - editor->make_bottom_panel_item_visible(region_editor); - } - } else { - if (region_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); - manually_hidden = false; - } - texture_region_button->hide(); - region_editor->edit(nullptr); - } +void EditorInspectorPluginTextureRegion::_region_edit(Object *p_object) { + texture_region_editor->edit(p_object); } -Dictionary TextureRegionEditorPlugin::get_state() const { - Dictionary state; - state["snap_offset"] = region_editor->snap_offset; - state["snap_step"] = region_editor->snap_step; - state["snap_separation"] = region_editor->snap_separation; - state["snap_mode"] = region_editor->snap_mode; - return state; -} - -void TextureRegionEditorPlugin::set_state(const Dictionary &p_state) { - Dictionary state = p_state; - if (state.has("snap_step")) { - Vector2 s = state["snap_step"]; - region_editor->sb_step_x->set_value(s.x); - region_editor->sb_step_y->set_value(s.y); - region_editor->snap_step = s; - } - - if (state.has("snap_offset")) { - Vector2 ofs = state["snap_offset"]; - region_editor->sb_off_x->set_value(ofs.x); - region_editor->sb_off_y->set_value(ofs.y); - region_editor->snap_offset = ofs; - } - - if (state.has("snap_separation")) { - Vector2 sep = state["snap_separation"]; - region_editor->sb_sep_x->set_value(sep.x); - region_editor->sb_sep_y->set_value(sep.y); - region_editor->snap_separation = sep; - } - - if (state.has("snap_mode")) { - region_editor->_set_snap_mode(state["snap_mode"]); - region_editor->snap_mode_button->select(state["snap_mode"]); +bool EditorInspectorPluginTextureRegion::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) { + if ((p_type == Variant::RECT2 || p_type == Variant::RECT2I)) { + if (((Object::cast_to<Sprite2D>(p_object) || Object::cast_to<Sprite3D>(p_object) || Object::cast_to<NinePatchRect>(p_object) || Object::cast_to<StyleBoxTexture>(p_object)) && p_path == "region_rect") || (Object::cast_to<AtlasTexture>(p_object) && p_path == "region")) { + Button *button = EditorInspector::create_inspector_action_button(TTR("Edit Region")); + button->set_icon(texture_region_editor->get_theme_icon(SNAME("RegionEdit"), SNAME("EditorIcons"))); + button->connect("pressed", callable_mp(this, &EditorInspectorPluginTextureRegion::_region_edit).bind(p_object)); + add_property_editor(p_path, button, true); + } } + return false; //not exclusive } -void TextureRegionEditorPlugin::_bind_methods() { +EditorInspectorPluginTextureRegion::EditorInspectorPluginTextureRegion() { + texture_region_editor = memnew(TextureRegionEditor); + EditorNode::get_singleton()->get_gui_base()->add_child(texture_region_editor); } -TextureRegionEditorPlugin::TextureRegionEditorPlugin(EditorNode *p_node) { - manually_hidden = false; - editor = p_node; - - region_editor = memnew(TextureRegionEditor(p_node)); - region_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE); - region_editor->hide(); - region_editor->connect("visibility_changed", callable_mp(this, &TextureRegionEditorPlugin::_editor_visiblity_changed)); - - texture_region_button = p_node->add_bottom_panel_item(TTR("TextureRegion"), region_editor); - texture_region_button->hide(); +TextureRegionEditorPlugin::TextureRegionEditorPlugin() { + Ref<EditorInspectorPluginTextureRegion> inspector_plugin; + inspector_plugin.instantiate(); + add_inspector_plugin(inspector_plugin); } diff --git a/editor/plugins/texture_region_editor_plugin.h b/editor/plugins/texture_region_editor_plugin.h index 23981ddb81..c303cec3f5 100644 --- a/editor/plugins/texture_region_editor_plugin.h +++ b/editor/plugins/texture_region_editor_plugin.h @@ -1,51 +1,51 @@ -/*************************************************************************/ -/* texture_region_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* texture_region_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 TEXTURE_REGION_EDITOR_PLUGIN_H #define TEXTURE_REGION_EDITOR_PLUGIN_H #include "canvas_item_editor_plugin.h" -#include "editor/editor_node.h" +#include "editor/editor_inspector.h" #include "editor/editor_plugin.h" #include "scene/2d/sprite_2d.h" #include "scene/3d/sprite_3d.h" +#include "scene/gui/dialogs.h" #include "scene/gui/nine_patch_rect.h" #include "scene/resources/style_box.h" #include "scene/resources/texture.h" -/** - @author Mariano Suligoy -*/ +class ViewPanner; +class OptionButton; -class TextureRegionEditor : public VBoxContainer { - GDCLASS(TextureRegionEditor, VBoxContainer); +class TextureRegionEditor : public AcceptDialog { + GDCLASS(TextureRegionEditor, AcceptDialog); enum SnapMode { SNAP_NONE, @@ -55,52 +55,56 @@ class TextureRegionEditor : public VBoxContainer { }; friend class TextureRegionEditorPlugin; - OptionButton *snap_mode_button; - Button *zoom_in; - Button *zoom_reset; - Button *zoom_out; - HBoxContainer *hb_grid; //For showing/hiding the grid controls when changing the SnapMode - SpinBox *sb_step_y; - SpinBox *sb_step_x; - SpinBox *sb_off_y; - SpinBox *sb_off_x; - SpinBox *sb_sep_y; - SpinBox *sb_sep_x; - Panel *edit_draw; - - VScrollBar *vscroll; - HScrollBar *hscroll; - - EditorNode *editor; - UndoRedo *undo_redo; + OptionButton *snap_mode_button = nullptr; + Button *zoom_in = nullptr; + Button *zoom_reset = nullptr; + Button *zoom_out = nullptr; + HBoxContainer *hb_grid = nullptr; //For showing/hiding the grid controls when changing the SnapMode + SpinBox *sb_step_y = nullptr; + SpinBox *sb_step_x = nullptr; + SpinBox *sb_off_y = nullptr; + SpinBox *sb_off_x = nullptr; + SpinBox *sb_sep_y = nullptr; + SpinBox *sb_sep_x = nullptr; + Panel *edit_draw = nullptr; + + VScrollBar *vscroll = nullptr; + HScrollBar *hscroll = nullptr; Vector2 draw_ofs; - float draw_zoom; - bool updating_scroll; + float draw_zoom = 0.0; + bool updating_scroll = false; - int snap_mode; + int snap_mode = 0; Vector2 snap_offset; Vector2 snap_step; Vector2 snap_separation; - Sprite2D *node_sprite_2d; - Sprite3D *node_sprite_3d; - NinePatchRect *node_ninepatch; + Sprite2D *node_sprite_2d = nullptr; + Sprite3D *node_sprite_3d = nullptr; + NinePatchRect *node_ninepatch = nullptr; Ref<StyleBoxTexture> obj_styleBox; Ref<AtlasTexture> atlas_tex; + Ref<CanvasTexture> preview_tex; + Rect2 rect; Rect2 rect_prev; - float prev_margin; - int edited_margin; - Map<RID, List<Rect2>> cache_map; + float prev_margin = 0.0f; + int edited_margin = 0; + HashMap<RID, List<Rect2>> cache_map; List<Rect2> autoslice_cache; - bool autoslice_is_dirty; + bool autoslice_is_dirty = false; - bool drag; - bool creating; + bool drag = false; + bool creating = false; Vector2 drag_from; - int drag_index; + int drag_index = 0; + bool request_center = false; + + Ref<ViewPanner> panner; + void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event); + void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event); void _set_snap_mode(int p_mode); void _set_snap_off_x(float p_val); @@ -138,32 +142,32 @@ public: Sprite3D *get_sprite_3d(); void edit(Object *p_obj); - TextureRegionEditor(EditorNode *p_editor); + TextureRegionEditor(); }; -class TextureRegionEditorPlugin : public EditorPlugin { - GDCLASS(TextureRegionEditorPlugin, EditorPlugin); +// - bool manually_hidden; - Button *texture_region_button; - TextureRegionEditor *region_editor; - EditorNode *editor; +class EditorInspectorPluginTextureRegion : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginTextureRegion, EditorInspectorPlugin); -protected: - static void _bind_methods(); + TextureRegionEditor *texture_region_editor = nullptr; + + void _region_edit(Object *p_object); - void _editor_visiblity_changed(); +public: + virtual bool can_handle(Object *p_object) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) override; + + EditorInspectorPluginTextureRegion(); +}; + +class TextureRegionEditorPlugin : public EditorPlugin { + GDCLASS(TextureRegionEditorPlugin, EditorPlugin); public: virtual String get_name() const override { return "TextureRegion"; } - bool has_main_screen() const override { return false; } - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - void set_state(const Dictionary &p_state) override; - Dictionary get_state() const override; - - TextureRegionEditorPlugin(EditorNode *p_node); + + TextureRegionEditorPlugin(); }; #endif // TEXTURE_REGION_EDITOR_PLUGIN_H diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 91c17399c2..073adb467a 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -1,39 +1,46 @@ -/*************************************************************************/ -/* theme_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* theme_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 "theme_editor_plugin.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_resource_picker.h" #include "editor/editor_scale.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/progress_dialog.h" +#include "scene/gui/color_picker.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/split_container.h" +#include "scene/theme/theme_db.h" void ThemeItemImportTree::_update_items_tree() { import_items_tree->clear(); @@ -68,9 +75,17 @@ void ThemeItemImportTree::_update_items_tree() { for (const StringName &E : types) { String type_name = (String)E; + Ref<Texture2D> type_icon; + if (E == "") { + type_icon = get_theme_icon(SNAME("NodeDisabled"), SNAME("EditorIcons")); + } else { + type_icon = EditorNode::get_singleton()->get_class_icon(E, "NodeDisabled"); + } + TreeItem *type_node = import_items_tree->create_item(root); type_node->set_meta("_can_be_imported", false); type_node->set_collapsed(true); + type_node->set_icon(0, type_icon); type_node->set_text(0, type_name); type_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK); type_node->set_checked(IMPORT_ITEM, false); @@ -81,8 +96,6 @@ void ThemeItemImportTree::_update_items_tree() { bool is_matching_filter = (filter_text.is_empty() || type_name.findn(filter_text) > -1); bool has_filtered_items = false; - bool any_checked = false; - bool any_checked_with_data = false; for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) { Theme::DataType dt = (Theme::DataType)i; @@ -123,7 +136,7 @@ void ThemeItemImportTree::_update_items_tree() { data_type_node->set_checked(IMPORT_ITEM_DATA, false); data_type_node->set_editable(IMPORT_ITEM_DATA, true); - List<TreeItem *> *item_list; + List<TreeItem *> *item_list = nullptr; switch (dt) { case Theme::DATA_TYPE_COLOR: @@ -178,9 +191,6 @@ void ThemeItemImportTree::_update_items_tree() { break; // Can't happen, but silences warning. } - bool data_type_any_checked = false; - bool data_type_any_checked_with_data = false; - filtered_names.sort_custom<StringName::AlphCompare>(); for (const StringName &F : filtered_names) { TreeItem *item_node = import_items_tree->create_item(data_type_node); @@ -194,20 +204,11 @@ void ThemeItemImportTree::_update_items_tree() { item_node->set_editable(IMPORT_ITEM_DATA, true); _restore_selected_item(item_node); - if (item_node->is_checked(IMPORT_ITEM)) { - data_type_any_checked = true; - any_checked = true; - } - if (item_node->is_checked(IMPORT_ITEM_DATA)) { - data_type_any_checked_with_data = true; - any_checked_with_data = true; - } + item_node->propagate_check(IMPORT_ITEM, false); + item_node->propagate_check(IMPORT_ITEM_DATA, false); item_list->push_back(item_node); } - - data_type_node->set_checked(IMPORT_ITEM, data_type_any_checked); - data_type_node->set_checked(IMPORT_ITEM_DATA, data_type_any_checked && data_type_any_checked_with_data); } // Remove the item if it doesn't match the filter in any way. @@ -221,15 +222,12 @@ void ThemeItemImportTree::_update_items_tree() { if (!filter_text.is_empty() && has_filtered_items) { type_node->set_collapsed(false); } - - type_node->set_checked(IMPORT_ITEM, any_checked); - type_node->set_checked(IMPORT_ITEM_DATA, any_checked && any_checked_with_data); } if (color_amount > 0) { Array arr; arr.push_back(color_amount); - select_colors_label->set_text(TTRN("One color", "{num} colors", color_amount).format(arr, "{num}")); + select_colors_label->set_text(TTRN("1 color", "{num} colors", color_amount).format(arr, "{num}")); select_all_colors_button->set_visible(true); select_full_colors_button->set_visible(true); deselect_all_colors_button->set_visible(true); @@ -243,7 +241,7 @@ void ThemeItemImportTree::_update_items_tree() { if (constant_amount > 0) { Array arr; arr.push_back(constant_amount); - select_constants_label->set_text(TTRN("One constant", "{num} constants", constant_amount).format(arr, "{num}")); + select_constants_label->set_text(TTRN("1 constant", "{num} constants", constant_amount).format(arr, "{num}")); select_all_constants_button->set_visible(true); select_full_constants_button->set_visible(true); deselect_all_constants_button->set_visible(true); @@ -257,7 +255,7 @@ void ThemeItemImportTree::_update_items_tree() { if (font_amount > 0) { Array arr; arr.push_back(font_amount); - select_fonts_label->set_text(TTRN("One font", "{num} fonts", font_amount).format(arr, "{num}")); + select_fonts_label->set_text(TTRN("1 font", "{num} fonts", font_amount).format(arr, "{num}")); select_all_fonts_button->set_visible(true); select_full_fonts_button->set_visible(true); deselect_all_fonts_button->set_visible(true); @@ -271,7 +269,7 @@ void ThemeItemImportTree::_update_items_tree() { if (font_size_amount > 0) { Array arr; arr.push_back(font_size_amount); - select_font_sizes_label->set_text(TTRN("One font size", "{num} font sizes", font_size_amount).format(arr, "{num}")); + select_font_sizes_label->set_text(TTRN("1 font size", "{num} font sizes", font_size_amount).format(arr, "{num}")); select_all_font_sizes_button->set_visible(true); select_full_font_sizes_button->set_visible(true); deselect_all_font_sizes_button->set_visible(true); @@ -285,7 +283,7 @@ void ThemeItemImportTree::_update_items_tree() { if (icon_amount > 0) { Array arr; arr.push_back(icon_amount); - select_icons_label->set_text(TTRN("One icon", "{num} icons", icon_amount).format(arr, "{num}")); + select_icons_label->set_text(TTRN("1 icon", "{num} icons", icon_amount).format(arr, "{num}")); select_all_icons_button->set_visible(true); select_full_icons_button->set_visible(true); deselect_all_icons_button->set_visible(true); @@ -301,7 +299,7 @@ void ThemeItemImportTree::_update_items_tree() { if (stylebox_amount > 0) { Array arr; arr.push_back(stylebox_amount); - select_styleboxes_label->set_text(TTRN("One stylebox", "{num} styleboxes", stylebox_amount).format(arr, "{num}")); + select_styleboxes_label->set_text(TTRN("1 stylebox", "{num} styleboxes", stylebox_amount).format(arr, "{num}")); select_all_styleboxes_button->set_visible(true); select_full_styleboxes_button->set_visible(true); deselect_all_styleboxes_button->set_visible(true); @@ -402,7 +400,7 @@ void ThemeItemImportTree::_restore_selected_item(TreeItem *p_tree_item) { void ThemeItemImportTree::_update_total_selected(Theme::DataType p_data_type) { ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds."); - Label *total_selected_items_label; + Label *total_selected_items_label = nullptr; switch (p_data_type) { case Theme::DATA_TYPE_COLOR: total_selected_items_label = total_selected_colors_label; @@ -471,23 +469,26 @@ void ThemeItemImportTree::_tree_item_edited() { if (is_checked) { if (edited_column == IMPORT_ITEM_DATA) { edited_item->set_checked(IMPORT_ITEM, true); + edited_item->propagate_check(IMPORT_ITEM); } - - _select_all_subitems(edited_item, (edited_column == IMPORT_ITEM_DATA)); } else { if (edited_column == IMPORT_ITEM) { edited_item->set_checked(IMPORT_ITEM_DATA, false); + edited_item->propagate_check(IMPORT_ITEM_DATA); } - - _deselect_all_subitems(edited_item, (edited_column == IMPORT_ITEM)); } - - _update_parent_items(edited_item); - _store_selected_item(edited_item); - + edited_item->propagate_check(edited_column); updating_tree = false; } +void ThemeItemImportTree::_check_propagated_to_tree_item(Object *p_obj, int p_column) { + TreeItem *item = Object::cast_to<TreeItem>(p_obj); + // Skip "category" tree items by checking for children. + if (item && !item->get_first_child()) { + _store_selected_item(item); + } +} + void ThemeItemImportTree::_select_all_subitems(TreeItem *p_root_item, bool p_select_with_data) { TreeItem *child_item = p_root_item->get_first_child(); while (child_item) { @@ -516,32 +517,6 @@ void ThemeItemImportTree::_deselect_all_subitems(TreeItem *p_root_item, bool p_d } } -void ThemeItemImportTree::_update_parent_items(TreeItem *p_root_item) { - TreeItem *parent_item = p_root_item->get_parent(); - if (!parent_item) { - return; - } - - bool any_checked = false; - bool any_checked_with_data = false; - - TreeItem *child_item = parent_item->get_first_child(); - while (child_item) { - if (child_item->is_checked(IMPORT_ITEM)) { - any_checked = true; - } - if (child_item->is_checked(IMPORT_ITEM_DATA)) { - any_checked_with_data = true; - } - - child_item = child_item->get_next(); - } - - parent_item->set_checked(IMPORT_ITEM, any_checked); - parent_item->set_checked(IMPORT_ITEM_DATA, any_checked && any_checked_with_data); - _update_parent_items(parent_item); -} - void ThemeItemImportTree::_select_all_items_pressed() { if (updating_tree) { return; @@ -589,7 +564,7 @@ void ThemeItemImportTree::_select_all_data_type_pressed(int p_data_type) { } Theme::DataType data_type = (Theme::DataType)p_data_type; - List<TreeItem *> *item_list; + List<TreeItem *> *item_list = nullptr; switch (data_type) { case Theme::DATA_TYPE_COLOR: @@ -629,7 +604,7 @@ void ThemeItemImportTree::_select_all_data_type_pressed(int p_data_type) { } child_item->set_checked(IMPORT_ITEM, true); - _update_parent_items(child_item); + child_item->propagate_check(IMPORT_ITEM, false); _store_selected_item(child_item); } @@ -644,7 +619,7 @@ void ThemeItemImportTree::_select_full_data_type_pressed(int p_data_type) { } Theme::DataType data_type = (Theme::DataType)p_data_type; - List<TreeItem *> *item_list; + List<TreeItem *> *item_list = nullptr; switch (data_type) { case Theme::DATA_TYPE_COLOR: @@ -685,7 +660,8 @@ void ThemeItemImportTree::_select_full_data_type_pressed(int p_data_type) { child_item->set_checked(IMPORT_ITEM, true); child_item->set_checked(IMPORT_ITEM_DATA, true); - _update_parent_items(child_item); + child_item->propagate_check(IMPORT_ITEM, false); + child_item->propagate_check(IMPORT_ITEM_DATA, false); _store_selected_item(child_item); } @@ -700,7 +676,7 @@ void ThemeItemImportTree::_deselect_all_data_type_pressed(int p_data_type) { } Theme::DataType data_type = (Theme::DataType)p_data_type; - List<TreeItem *> *item_list; + List<TreeItem *> *item_list = nullptr; switch (data_type) { case Theme::DATA_TYPE_COLOR: @@ -741,7 +717,8 @@ void ThemeItemImportTree::_deselect_all_data_type_pressed(int p_data_type) { child_item->set_checked(IMPORT_ITEM, false); child_item->set_checked(IMPORT_ITEM_DATA, false); - _update_parent_items(child_item); + child_item->propagate_check(IMPORT_ITEM, false); + child_item->propagate_check(IMPORT_ITEM_DATA, false); _store_selected_item(child_item); } @@ -823,7 +800,7 @@ void ThemeItemImportTree::_import_selected() { ProgressDialog::get_singleton()->end_task("import_theme_items"); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Import Theme Items")); ur->add_do_method(*edited_theme, "clear"); @@ -870,6 +847,8 @@ void ThemeItemImportTree::_notification(int p_what) { select_icons_warning_icon->set_texture(get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons"))); select_icons_warning->add_theme_color_override("font_color", get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"))); + import_items_filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); + // Bottom panel buttons. import_collapse_types_button->set_icon(get_theme_icon(SNAME("CollapseTree"), SNAME("EditorIcons"))); import_expand_types_button->set_icon(get_theme_icon(SNAME("ExpandTree"), SNAME("EditorIcons"))); @@ -917,15 +896,10 @@ void ThemeItemImportTree::_bind_methods() { } ThemeItemImportTree::ThemeItemImportTree() { - HBoxContainer *import_items_filter_hb = memnew(HBoxContainer); - add_child(import_items_filter_hb); - Label *import_items_filter_label = memnew(Label); - import_items_filter_label->set_text(TTR("Filter:")); - import_items_filter_hb->add_child(import_items_filter_label); import_items_filter = memnew(LineEdit); + import_items_filter->set_placeholder(TTR("Filter Items")); import_items_filter->set_clear_button_enabled(true); - import_items_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL); - import_items_filter_hb->add_child(import_items_filter); + add_child(import_items_filter); import_items_filter->connect("text_changed", callable_mp(this, &ThemeItemImportTree::_filter_text_changed)); HBoxContainer *import_main_hb = memnew(HBoxContainer); @@ -937,6 +911,7 @@ ThemeItemImportTree::ThemeItemImportTree() { import_items_tree->set_h_size_flags(Control::SIZE_EXPAND_FILL); import_main_hb->add_child(import_items_tree); import_items_tree->connect("item_edited", callable_mp(this, &ThemeItemImportTree::_tree_item_edited)); + import_items_tree->connect("check_propagated_to_item", callable_mp(this, &ThemeItemImportTree::_check_propagated_to_tree_item)); import_items_tree->set_columns(3); import_items_tree->set_column_titles_visible(true); @@ -1009,17 +984,17 @@ ThemeItemImportTree::ThemeItemImportTree() { for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) { Theme::DataType dt = (Theme::DataType)i; - TextureRect *select_items_icon; - Label *select_items_label; - Button *deselect_all_items_button; - Button *select_all_items_button; - Button *select_full_items_button; - Label *total_selected_items_label; + TextureRect *select_items_icon = nullptr; + Label *select_items_label = nullptr; + Button *deselect_all_items_button = nullptr; + Button *select_all_items_button = nullptr; + Button *select_full_items_button = nullptr; + Label *total_selected_items_label = nullptr; - String items_title = ""; - String select_all_items_tooltip = ""; - String select_full_items_tooltip = ""; - String deselect_all_items_tooltip = ""; + String items_title; + String select_all_items_tooltip; + String select_full_items_tooltip; + String deselect_all_items_tooltip; switch (dt) { case Theme::DATA_TYPE_COLOR: @@ -1131,17 +1106,17 @@ ThemeItemImportTree::ThemeItemImportTree() { button_set->set_alignment(BoxContainer::ALIGNMENT_END); all_set->add_child(button_set); select_all_items_button->set_flat(true); - select_all_items_button->set_tooltip(select_all_items_tooltip); + select_all_items_button->set_tooltip_text(select_all_items_tooltip); button_set->add_child(select_all_items_button); - select_all_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_all_data_type_pressed), varray(i)); + select_all_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_all_data_type_pressed).bind(i)); select_full_items_button->set_flat(true); - select_full_items_button->set_tooltip(select_full_items_tooltip); + select_full_items_button->set_tooltip_text(select_full_items_tooltip); button_set->add_child(select_full_items_button); - select_full_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_full_data_type_pressed), varray(i)); + select_full_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_full_data_type_pressed).bind(i)); deselect_all_items_button->set_flat(true); - deselect_all_items_button->set_tooltip(deselect_all_items_tooltip); + deselect_all_items_button->set_tooltip_text(deselect_all_items_tooltip); button_set->add_child(deselect_all_items_button); - deselect_all_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_deselect_all_data_type_pressed), varray(i)); + deselect_all_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_deselect_all_data_type_pressed).bind(i)); total_selected_items_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); total_selected_items_label->hide(); @@ -1157,7 +1132,7 @@ ThemeItemImportTree::ThemeItemImportTree() { select_icons_warning = memnew(Label); select_icons_warning->set_text(TTR("Caution: Adding icon data may considerably increase the size of your Theme resource.")); - select_icons_warning->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART); + select_icons_warning->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); select_icons_warning->set_h_size_flags(Control::SIZE_EXPAND_FILL); select_icons_warning_hb->add_child(select_icons_warning); } @@ -1170,33 +1145,33 @@ ThemeItemImportTree::ThemeItemImportTree() { import_collapse_types_button = memnew(Button); import_collapse_types_button->set_flat(true); - import_collapse_types_button->set_tooltip(TTR("Collapse types.")); + import_collapse_types_button->set_tooltip_text(TTR("Collapse types.")); import_buttons->add_child(import_collapse_types_button); - import_collapse_types_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_toggle_type_items), varray(true)); + import_collapse_types_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_toggle_type_items).bind(true)); import_expand_types_button = memnew(Button); import_expand_types_button->set_flat(true); - import_expand_types_button->set_tooltip(TTR("Expand types.")); + import_expand_types_button->set_tooltip_text(TTR("Expand types.")); import_buttons->add_child(import_expand_types_button); - import_expand_types_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_toggle_type_items), varray(false)); + import_expand_types_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_toggle_type_items).bind(false)); import_buttons->add_child(memnew(VSeparator)); import_select_all_button = memnew(Button); import_select_all_button->set_flat(true); import_select_all_button->set_text(TTR("Select All")); - import_select_all_button->set_tooltip(TTR("Select all Theme items.")); + import_select_all_button->set_tooltip_text(TTR("Select all Theme items.")); import_buttons->add_child(import_select_all_button); import_select_all_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_all_items_pressed)); import_select_full_button = memnew(Button); import_select_full_button->set_flat(true); import_select_full_button->set_text(TTR("Select With Data")); - import_select_full_button->set_tooltip(TTR("Select all Theme items with item data.")); + import_select_full_button->set_tooltip_text(TTR("Select all Theme items with item data.")); import_buttons->add_child(import_select_full_button); import_select_full_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_full_items_pressed)); import_deselect_all_button = memnew(Button); import_deselect_all_button->set_flat(true); import_deselect_all_button->set_text(TTR("Deselect All")); - import_deselect_all_button->set_tooltip(TTR("Deselect all Theme items.")); + import_deselect_all_button->set_tooltip_text(TTR("Deselect all Theme items.")); import_buttons->add_child(import_deselect_all_button); import_deselect_all_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_deselect_all_items_pressed)); @@ -1208,10 +1183,12 @@ ThemeItemImportTree::ThemeItemImportTree() { import_add_selected_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_import_selected)); } +/////////////////////// + void ThemeItemEditorDialog::ok_pressed() { if (import_default_theme_items->has_selected_items() || import_editor_theme_items->has_selected_items() || import_other_theme_items->has_selected_items()) { confirm_closing_dialog->set_text(TTR("Import Items tab has some items selected. Selection will be lost upon closing this window.\nClose anyway?")); - confirm_closing_dialog->popup_centered(Size2i(380, 120) * EDSCALE); + confirm_closing_dialog->popup_centered(Size2(380, 120) * EDSCALE); return; } @@ -1228,7 +1205,7 @@ void ThemeItemEditorDialog::_dialog_about_to_show() { _update_edit_types(); import_default_theme_items->set_edited_theme(edited_theme); - import_default_theme_items->set_base_theme(Theme::get_default()); + import_default_theme_items->set_base_theme(ThemeDB::get_singleton()->get_default_theme()); import_default_theme_items->reset_item_tree(); import_editor_theme_items->set_edited_theme(edited_theme); @@ -1240,7 +1217,7 @@ void ThemeItemEditorDialog::_dialog_about_to_show() { } void ThemeItemEditorDialog::_update_edit_types() { - Ref<Theme> base_theme = Theme::get_default(); + Ref<Theme> base_theme = ThemeDB::get_singleton()->get_default_theme(); List<StringName> theme_types; edited_theme->get_type_list(&theme_types); @@ -1248,7 +1225,8 @@ void ThemeItemEditorDialog::_update_edit_types() { bool item_reselected = false; edit_type_list->clear(); - int e_idx = 0; + TreeItem *list_root = edit_type_list->create_item(); + for (const StringName &E : theme_types) { Ref<Texture2D> item_icon; if (E == "") { @@ -1256,19 +1234,21 @@ void ThemeItemEditorDialog::_update_edit_types() { } else { item_icon = EditorNode::get_singleton()->get_class_icon(E, "NodeDisabled"); } - edit_type_list->add_item(E, item_icon); + TreeItem *list_item = edit_type_list->create_item(list_root); + list_item->set_text(0, E); + list_item->set_icon(0, item_icon); + list_item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TYPES_TREE_REMOVE_ITEM, false, TTR("Remove Type")); if (E == edited_item_type) { - edit_type_list->select(e_idx); + list_item->select(0); item_reselected = true; } - e_idx++; } if (!item_reselected) { edited_item_type = ""; - if (edit_type_list->get_item_count() > 0) { - edit_type_list->select(0); + if (list_root->get_child_count() > 0) { + list_root->get_child(0)->select(0); } } @@ -1277,9 +1257,9 @@ void ThemeItemEditorDialog::_update_edit_types() { default_types.sort_custom<StringName::AlphCompare>(); String selected_type = ""; - Vector<int> selected_ids = edit_type_list->get_selected_items(); - if (selected_ids.size() > 0) { - selected_type = edit_type_list->get_item_text(selected_ids[0]); + TreeItem *selected_item = edit_type_list->get_selected(); + if (selected_item) { + selected_type = selected_item->get_text(0); edit_items_add_color->set_disabled(false); edit_items_add_constant->set_disabled(false); @@ -1313,11 +1293,30 @@ void ThemeItemEditorDialog::_update_edit_types() { _update_edit_item_tree(selected_type); } -void ThemeItemEditorDialog::_edited_type_selected(int p_item_idx) { - String selected_type = edit_type_list->get_item_text(p_item_idx); +void ThemeItemEditorDialog::_edited_type_selected() { + TreeItem *selected_item = edit_type_list->get_selected(); + String selected_type = selected_item->get_text(0); _update_edit_item_tree(selected_type); } +void ThemeItemEditorDialog::_edited_type_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) { + if (p_button != MouseButton::LEFT) { + return; + } + + TreeItem *item = Object::cast_to<TreeItem>(p_item); + if (!item) { + return; + } + + switch (p_id) { + case TYPES_TREE_REMOVE_ITEM: { + String type_name = item->get_text(0); + _remove_theme_type(type_name); + } break; + } +} + void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) { edited_item_type = p_item_type; @@ -1466,8 +1465,8 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) { } // If some type is selected, but it doesn't seem to have any items, show a guiding message. - Vector<int> selected_ids = edit_type_list->get_selected_items(); - if (selected_ids.size() > 0) { + TreeItem *selected_item = edit_type_list->get_selected(); + if (selected_item) { if (!has_any_items) { edit_items_message->set_text(TTR("This theme type is empty.\nAdd more items to it manually or by importing from another theme.")); edit_items_message->show(); @@ -1478,7 +1477,11 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) { } } -void ThemeItemEditorDialog::_item_tree_button_pressed(Object *p_item, int p_column, int p_id) { +void ThemeItemEditorDialog::_item_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) { + if (p_button != MouseButton::LEFT) { + return; + } + TreeItem *item = Object::cast_to<TreeItem>(p_item); if (!item) { return; @@ -1495,7 +1498,7 @@ void ThemeItemEditorDialog::_item_tree_button_pressed(Object *p_item, int p_colu String item_name = item->get_text(0); int data_type = item->get_parent()->get_metadata(0); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Remove Theme Item")); ur->add_do_method(*edited_theme, "clear_theme_item", (Theme::DataType)data_type, item_name, edited_item_type); ur->add_undo_method(*edited_theme, "set_theme_item", (Theme::DataType)data_type, item_name, edited_item_type, edited_theme->get_theme_item((Theme::DataType)data_type, item_name, edited_item_type)); @@ -1514,20 +1517,19 @@ void ThemeItemEditorDialog::_add_theme_type(const String &p_new_text) { const String new_type = edit_add_type_value->get_text().strip_edges(); edit_add_type_value->clear(); - edited_theme->add_icon_type(new_type); - edited_theme->add_stylebox_type(new_type); - edited_theme->add_font_type(new_type); - edited_theme->add_font_size_type(new_type); - edited_theme->add_color_type(new_type); - edited_theme->add_constant_type(new_type); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Add Theme Type")); - _update_edit_types(); + ur->add_do_method(*edited_theme, "add_type", new_type); + ur->add_undo_method(*edited_theme, "remove_type", new_type); + ur->add_do_method(this, "_update_edit_types"); + ur->add_undo_method(this, "_update_edit_types"); - edited_theme->emit_changed(); + ur->commit_action(); } void ThemeItemEditorDialog::_add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Create Theme Item")); switch (p_data_type) { @@ -1568,13 +1570,34 @@ void ThemeItemEditorDialog::_add_theme_item(Theme::DataType p_data_type, String ur->commit_action(); } +void ThemeItemEditorDialog::_remove_theme_type(const String &p_theme_type) { + Ref<Theme> old_snapshot = edited_theme->duplicate(); + Ref<Theme> new_snapshot = edited_theme->duplicate(); + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Remove Theme Type")); + + new_snapshot->remove_type(p_theme_type); + + ur->add_do_method(*edited_theme, "clear"); + ur->add_do_method(*edited_theme, "merge_with", new_snapshot); + // If the type was empty, it cannot be restored with merge, but thankfully we can fake it. + ur->add_undo_method(*edited_theme, "add_type", p_theme_type); + ur->add_undo_method(*edited_theme, "merge_with", old_snapshot); + + ur->add_do_method(this, "_update_edit_types"); + ur->add_undo_method(this, "_update_edit_types"); + + ur->commit_action(); +} + void ThemeItemEditorDialog::_remove_data_type_items(Theme::DataType p_data_type, String p_item_type) { List<StringName> names; Ref<Theme> old_snapshot = edited_theme->duplicate(); Ref<Theme> new_snapshot = edited_theme->duplicate(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Remove Data Type Items From Theme")); new_snapshot->get_theme_item_list(p_data_type, p_item_type, &names); @@ -1603,14 +1626,14 @@ void ThemeItemEditorDialog::_remove_class_items() { Ref<Theme> old_snapshot = edited_theme->duplicate(); Ref<Theme> new_snapshot = edited_theme->duplicate(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Remove Class Items From Theme")); for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) { Theme::DataType data_type = (Theme::DataType)dt; names.clear(); - Theme::get_default()->get_theme_item_list(data_type, edited_item_type, &names); + ThemeDB::get_singleton()->get_default_theme()->get_theme_item_list(data_type, edited_item_type, &names); for (const StringName &E : names) { if (new_snapshot->has_theme_item_nocheck(data_type, E, edited_item_type)) { new_snapshot->clear_theme_item(data_type, E, edited_item_type); @@ -1639,7 +1662,7 @@ void ThemeItemEditorDialog::_remove_custom_items() { Ref<Theme> old_snapshot = edited_theme->duplicate(); Ref<Theme> new_snapshot = edited_theme->duplicate(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Remove Custom Items From Theme")); for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) { @@ -1648,7 +1671,7 @@ void ThemeItemEditorDialog::_remove_custom_items() { names.clear(); new_snapshot->get_theme_item_list(data_type, edited_item_type, &names); for (const StringName &E : names) { - if (!Theme::get_default()->has_theme_item_nocheck(data_type, E, edited_item_type)) { + if (!ThemeDB::get_singleton()->get_default_theme()->has_theme_item_nocheck(data_type, E, edited_item_type)) { new_snapshot->clear_theme_item(data_type, E, edited_item_type); if (dt == Theme::DATA_TYPE_STYLEBOX && theme_type_editor->is_stylebox_pinned(edited_theme->get_stylebox(E, edited_item_type))) { @@ -1675,7 +1698,7 @@ void ThemeItemEditorDialog::_remove_all_items() { Ref<Theme> old_snapshot = edited_theme->duplicate(); Ref<Theme> new_snapshot = edited_theme->duplicate(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Remove All Items From Theme")); for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) { @@ -1779,7 +1802,7 @@ void ThemeItemEditorDialog::_confirm_edit_theme_item() { if (item_popup_mode == CREATE_THEME_ITEM) { _add_theme_item(edit_item_data_type, theme_item_name->get_text(), edited_item_type); } else if (item_popup_mode == RENAME_THEME_ITEM) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Rename Theme Item")); ur->add_do_method(*edited_theme, "rename_theme_item", edit_item_data_type, edit_item_old_name, theme_item_name->get_text(), edited_item_type); @@ -1859,10 +1882,9 @@ void ThemeItemEditorDialog::_notification(int p_what) { edit_items_remove_custom->set_icon(get_theme_icon(SNAME("ThemeRemoveCustomItems"), SNAME("EditorIcons"))); edit_items_remove_all->set_icon(get_theme_icon(SNAME("ThemeRemoveAllItems"), SNAME("EditorIcons"))); - import_another_theme_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); + edit_add_type_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - tc->add_theme_style_override("tab_selected", get_theme_stylebox(SNAME("tab_selected_odd"), SNAME("TabContainer"))); - tc->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel_odd"), SNAME("TabContainer"))); + import_another_theme_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); } break; } } @@ -1878,14 +1900,14 @@ void ThemeItemEditorDialog::set_edited_theme(const Ref<Theme> &p_theme) { ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_editor) { set_title(TTR("Manage Theme Items")); - get_ok_button()->set_text(TTR("Close")); + set_ok_button_text(TTR("Close")); set_hide_on_ok(false); // Closing may require a confirmation in some cases. theme_type_editor = p_theme_type_editor; tc = memnew(TabContainer); - tc->set_tab_alignment(TabContainer::ALIGNMENT_LEFT); add_child(tc); + tc->set_theme_type_variation("TabContainerOdd"); // Edit Items tab. HSplitContainer *edit_dialog_hs = memnew(HSplitContainer); @@ -1900,10 +1922,14 @@ ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_edito edit_type_label->set_text(TTR("Types:")); edit_dialog_side_vb->add_child(edit_type_label); - edit_type_list = memnew(ItemList); + edit_type_list = memnew(Tree); + edit_type_list->set_hide_root(true); + edit_type_list->set_hide_folding(true); + edit_type_list->set_columns(1); edit_type_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); edit_dialog_side_vb->add_child(edit_type_list); edit_type_list->connect("item_selected", callable_mp(this, &ThemeItemEditorDialog::_edited_type_selected)); + edit_type_list->connect("button_clicked", callable_mp(this, &ThemeItemEditorDialog::_edited_type_button_pressed)); Label *edit_add_type_label = memnew(Label); edit_add_type_label->set_text(TTR("Add Type:")); @@ -1915,10 +1941,9 @@ ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_edito edit_add_type_value->set_h_size_flags(Control::SIZE_EXPAND_FILL); edit_add_type_value->connect("text_submitted", callable_mp(this, &ThemeItemEditorDialog::_add_theme_type)); edit_add_type_hb->add_child(edit_add_type_value); - Button *edit_add_type_button = memnew(Button); - edit_add_type_button->set_text(TTR("Add")); + edit_add_type_button = memnew(Button); edit_add_type_hb->add_child(edit_add_type_button); - edit_add_type_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_add_theme_type), varray("")); + edit_add_type_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_add_theme_type).bind("")); VBoxContainer *edit_items_vb = memnew(VBoxContainer); edit_items_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -1932,46 +1957,46 @@ ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_edito edit_items_toolbar->add_child(edit_items_toolbar_add_label); edit_items_add_color = memnew(Button); - edit_items_add_color->set_tooltip(TTR("Add Color Item")); + edit_items_add_color->set_tooltip_text(TTR("Add Color Item")); edit_items_add_color->set_flat(true); edit_items_add_color->set_disabled(true); edit_items_toolbar->add_child(edit_items_add_color); - edit_items_add_color->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog), varray(Theme::DATA_TYPE_COLOR)); + edit_items_add_color->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog).bind(Theme::DATA_TYPE_COLOR)); edit_items_add_constant = memnew(Button); - edit_items_add_constant->set_tooltip(TTR("Add Constant Item")); + edit_items_add_constant->set_tooltip_text(TTR("Add Constant Item")); edit_items_add_constant->set_flat(true); edit_items_add_constant->set_disabled(true); edit_items_toolbar->add_child(edit_items_add_constant); - edit_items_add_constant->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog), varray(Theme::DATA_TYPE_CONSTANT)); + edit_items_add_constant->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog).bind(Theme::DATA_TYPE_CONSTANT)); edit_items_add_font = memnew(Button); - edit_items_add_font->set_tooltip(TTR("Add Font Item")); + edit_items_add_font->set_tooltip_text(TTR("Add Font Item")); edit_items_add_font->set_flat(true); edit_items_add_font->set_disabled(true); edit_items_toolbar->add_child(edit_items_add_font); - edit_items_add_font->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog), varray(Theme::DATA_TYPE_FONT)); + edit_items_add_font->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog).bind(Theme::DATA_TYPE_FONT)); edit_items_add_font_size = memnew(Button); - edit_items_add_font_size->set_tooltip(TTR("Add Font Size Item")); + edit_items_add_font_size->set_tooltip_text(TTR("Add Font Size Item")); edit_items_add_font_size->set_flat(true); edit_items_add_font_size->set_disabled(true); edit_items_toolbar->add_child(edit_items_add_font_size); - edit_items_add_font_size->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog), varray(Theme::DATA_TYPE_FONT_SIZE)); + edit_items_add_font_size->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog).bind(Theme::DATA_TYPE_FONT_SIZE)); edit_items_add_icon = memnew(Button); - edit_items_add_icon->set_tooltip(TTR("Add Icon Item")); + edit_items_add_icon->set_tooltip_text(TTR("Add Icon Item")); edit_items_add_icon->set_flat(true); edit_items_add_icon->set_disabled(true); edit_items_toolbar->add_child(edit_items_add_icon); - edit_items_add_icon->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog), varray(Theme::DATA_TYPE_ICON)); + edit_items_add_icon->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog).bind(Theme::DATA_TYPE_ICON)); edit_items_add_stylebox = memnew(Button); - edit_items_add_stylebox->set_tooltip(TTR("Add StyleBox Item")); + edit_items_add_stylebox->set_tooltip_text(TTR("Add StyleBox Item")); edit_items_add_stylebox->set_flat(true); edit_items_add_stylebox->set_disabled(true); edit_items_toolbar->add_child(edit_items_add_stylebox); - edit_items_add_stylebox->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog), varray(Theme::DATA_TYPE_STYLEBOX)); + edit_items_add_stylebox->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog).bind(Theme::DATA_TYPE_STYLEBOX)); edit_items_toolbar->add_child(memnew(VSeparator)); @@ -1980,21 +2005,21 @@ ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_edito edit_items_toolbar->add_child(edit_items_toolbar_remove_label); edit_items_remove_class = memnew(Button); - edit_items_remove_class->set_tooltip(TTR("Remove Class Items")); + edit_items_remove_class->set_tooltip_text(TTR("Remove Class Items")); edit_items_remove_class->set_flat(true); edit_items_remove_class->set_disabled(true); edit_items_toolbar->add_child(edit_items_remove_class); edit_items_remove_class->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_remove_class_items)); edit_items_remove_custom = memnew(Button); - edit_items_remove_custom->set_tooltip(TTR("Remove Custom Items")); + edit_items_remove_custom->set_tooltip_text(TTR("Remove Custom Items")); edit_items_remove_custom->set_flat(true); edit_items_remove_custom->set_disabled(true); edit_items_toolbar->add_child(edit_items_remove_custom); edit_items_remove_custom->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_remove_custom_items)); edit_items_remove_all = memnew(Button); - edit_items_remove_all->set_tooltip(TTR("Remove All Items")); + edit_items_remove_all->set_tooltip_text(TTR("Remove All Items")); edit_items_remove_all->set_flat(true); edit_items_remove_all->set_disabled(true); edit_items_toolbar->add_child(edit_items_remove_all); @@ -2005,14 +2030,14 @@ ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_edito edit_items_tree->set_hide_root(true); edit_items_tree->set_columns(1); edit_items_vb->add_child(edit_items_tree); - edit_items_tree->connect("button_pressed", callable_mp(this, &ThemeItemEditorDialog::_item_tree_button_pressed)); + edit_items_tree->connect("button_clicked", callable_mp(this, &ThemeItemEditorDialog::_item_tree_button_pressed)); edit_items_message = memnew(Label); - edit_items_message->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + edit_items_message->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); edit_items_message->set_mouse_filter(Control::MOUSE_FILTER_STOP); edit_items_message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); edit_items_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); - edit_items_message->set_autowrap_mode(Label::AUTOWRAP_WORD); + edit_items_message->set_autowrap_mode(TextServer::AUTOWRAP_WORD); edit_items_tree->add_child(edit_items_message); edit_theme_item_dialog = memnew(ConfirmationDialog); @@ -2039,6 +2064,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_edito // Import Items tab. TabContainer *import_tc = memnew(TabContainer); + import_tc->set_tab_alignment(TabBar::ALIGNMENT_CENTER); tc->add_child(import_tc); tc->set_tab_title(1, TTR("Import Items")); @@ -2070,7 +2096,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_edito List<String> ext; ResourceLoader::get_recognized_extensions_for_type("Theme", &ext); for (const String &E : ext) { - import_another_theme_dialog->add_filter("*." + E + "; Theme Resource"); + import_another_theme_dialog->add_filter("*." + E, TTR("Theme Resource")); } import_another_file_hb->add_child(import_another_theme_dialog); import_another_theme_dialog->connect("file_selected", callable_mp(this, &ThemeItemEditorDialog::_select_another_theme_cbk)); @@ -2089,6 +2115,8 @@ ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_edito confirm_closing_dialog->connect("confirmed", callable_mp(this, &ThemeItemEditorDialog::_close_dialog)); } +/////////////////////// + void ThemeTypeDialog::_dialog_about_to_show() { add_type_filter->set_text(""); add_type_filter->grab_focus(); @@ -2104,7 +2132,7 @@ void ThemeTypeDialog::_update_add_type_options(const String &p_filter) { add_type_options->clear(); List<StringName> names; - Theme::get_default()->get_type_list(&names); + ThemeDB::get_singleton()->get_default_theme()->get_type_list(&names); if (include_own_types) { edited_theme->get_type_list(&names); } @@ -2113,7 +2141,7 @@ void ThemeTypeDialog::_update_add_type_options(const String &p_filter) { Vector<StringName> unique_names; for (const StringName &E : names) { // Filter out undesired values. - if (!p_filter.is_subsequence_ofi(String(E))) { + if (!p_filter.is_subsequence_ofn(String(E))) { continue; } @@ -2227,6 +2255,8 @@ ThemeTypeDialog::ThemeTypeDialog() { add_child(add_type_confirmation); } +/////////////////////// + VBoxContainer *ThemeTypeEditor::_create_item_list(Theme::DataType p_data_type) { VBoxContainer *items_tab = memnew(VBoxContainer); items_tab->set_custom_minimum_size(Size2(0, 160) * EDSCALE); @@ -2246,11 +2276,11 @@ VBoxContainer *ThemeTypeEditor::_create_item_list(Theme::DataType p_data_type) { LineEdit *item_add_edit = memnew(LineEdit); item_add_edit->set_h_size_flags(SIZE_EXPAND_FILL); item_add_hb->add_child(item_add_edit); - item_add_edit->connect("text_submitted", callable_mp(this, &ThemeTypeEditor::_item_add_lineedit_cbk), varray(p_data_type, item_add_edit)); + item_add_edit->connect("text_submitted", callable_mp(this, &ThemeTypeEditor::_item_add_lineedit_cbk).bind(p_data_type, item_add_edit)); Button *item_add_button = memnew(Button); item_add_button->set_text(TTR("Add")); item_add_hb->add_child(item_add_button); - item_add_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_add_cbk), varray(p_data_type, item_add_edit)); + item_add_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_add_cbk).bind(p_data_type, item_add_edit)); return items_list; } @@ -2263,7 +2293,7 @@ void ThemeTypeEditor::_update_type_list() { } updating = true; - Control *focused = get_focus_owner(); + Control *focused = get_viewport()->gui_get_focus_owner(); if (focused) { if (focusables.has(focused)) { // If focus is currently on one of the internal property editors, don't update. @@ -2332,8 +2362,8 @@ void ThemeTypeEditor::_update_type_list_debounced() { update_debounce_timer->start(); } -OrderedHashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default) { - OrderedHashMap<StringName, bool> items; +HashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default) { + HashMap<StringName, bool> items; List<StringName> names; if (include_default) { @@ -2343,7 +2373,7 @@ OrderedHashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_ default_type = edited_theme->get_type_variation_base(p_type_name); } - (Theme::get_default().operator->()->*get_list_func)(default_type, &names); + (ThemeDB::get_singleton()->get_default_theme().operator->()->*get_list_func)(default_type, &names); names.sort_custom<StringName::AlphCompare>(); for (const StringName &E : names) { items[E] = false; @@ -2360,12 +2390,12 @@ OrderedHashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_ } List<StringName> keys; - for (OrderedHashMap<StringName, bool>::Element E = items.front(); E; E = E.next()) { - keys.push_back(E.key()); + for (const KeyValue<StringName, bool> &E : items) { + keys.push_back(E.key); } keys.sort_custom<StringName::AlphCompare>(); - OrderedHashMap<StringName, bool> ordered_items; + HashMap<StringName, bool> ordered_items; for (const StringName &E : keys) { ordered_items[E] = items[E]; } @@ -2385,7 +2415,7 @@ HBoxContainer *ThemeTypeEditor::_create_property_control(Theme::DataType p_data_ item_name->set_h_size_flags(SIZE_EXPAND_FILL); item_name->set_clip_text(true); item_name->set_text(p_item_name); - item_name->set_tooltip(p_item_name); + item_name->set_tooltip_text(p_item_name); item_name_container->add_child(item_name); if (p_editable) { @@ -2393,47 +2423,47 @@ HBoxContainer *ThemeTypeEditor::_create_property_control(Theme::DataType p_data_ item_name_edit->set_h_size_flags(SIZE_EXPAND_FILL); item_name_edit->set_text(p_item_name); item_name_container->add_child(item_name_edit); - item_name_edit->connect("text_submitted", callable_mp(this, &ThemeTypeEditor::_item_rename_entered), varray(p_data_type, p_item_name, item_name_container)); + item_name_edit->connect("text_submitted", callable_mp(this, &ThemeTypeEditor::_item_rename_entered).bind(p_data_type, p_item_name, item_name_container)); item_name_edit->hide(); Button *item_rename_button = memnew(Button); item_rename_button->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); - item_rename_button->set_tooltip(TTR("Rename Item")); + item_rename_button->set_tooltip_text(TTR("Rename Item")); item_rename_button->set_flat(true); item_name_container->add_child(item_rename_button); - item_rename_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_cbk), varray(p_data_type, p_item_name, item_name_container)); + item_rename_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_cbk).bind(p_data_type, p_item_name, item_name_container)); Button *item_remove_button = memnew(Button); item_remove_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - item_remove_button->set_tooltip(TTR("Remove Item")); + item_remove_button->set_tooltip_text(TTR("Remove Item")); item_remove_button->set_flat(true); item_name_container->add_child(item_remove_button); - item_remove_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_remove_cbk), varray(p_data_type, p_item_name)); + item_remove_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_remove_cbk).bind(p_data_type, p_item_name)); Button *item_rename_confirm_button = memnew(Button); item_rename_confirm_button->set_icon(get_theme_icon(SNAME("ImportCheck"), SNAME("EditorIcons"))); - item_rename_confirm_button->set_tooltip(TTR("Confirm Item Rename")); + item_rename_confirm_button->set_tooltip_text(TTR("Confirm Item Rename")); item_rename_confirm_button->set_flat(true); item_name_container->add_child(item_rename_confirm_button); - item_rename_confirm_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_confirmed), varray(p_data_type, p_item_name, item_name_container)); + item_rename_confirm_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_confirmed).bind(p_data_type, p_item_name, item_name_container)); item_rename_confirm_button->hide(); Button *item_rename_cancel_button = memnew(Button); item_rename_cancel_button->set_icon(get_theme_icon(SNAME("ImportFail"), SNAME("EditorIcons"))); - item_rename_cancel_button->set_tooltip(TTR("Cancel Item Rename")); + item_rename_cancel_button->set_tooltip_text(TTR("Cancel Item Rename")); item_rename_cancel_button->set_flat(true); item_name_container->add_child(item_rename_cancel_button); - item_rename_cancel_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_canceled), varray(p_data_type, p_item_name, item_name_container)); + item_rename_cancel_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_canceled).bind(p_data_type, p_item_name, item_name_container)); item_rename_cancel_button->hide(); } else { item_name->add_theme_color_override("font_color", get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"))); Button *item_override_button = memnew(Button); item_override_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - item_override_button->set_tooltip(TTR("Override Item")); + item_override_button->set_tooltip_text(TTR("Override Item")); item_override_button->set_flat(true); item_name_container->add_child(item_override_button); - item_override_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_override_cbk), varray(p_data_type, p_item_name)); + item_override_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_override_cbk).bind(p_data_type, p_item_name)); } return item_control; @@ -2453,22 +2483,23 @@ void ThemeTypeEditor::_update_type_items() { { for (int i = color_items_list->get_child_count() - 1; i >= 0; i--) { Node *node = color_items_list->get_child(i); - node->queue_delete(); + node->queue_free(); color_items_list->remove_child(node); } - OrderedHashMap<StringName, bool> color_items = _get_type_items(edited_type, &Theme::get_color_list, show_default); - for (OrderedHashMap<StringName, bool>::Element E = color_items.front(); E; E = E.next()) { - HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_COLOR, E.key(), E.get()); + HashMap<StringName, bool> color_items = _get_type_items(edited_type, &Theme::get_color_list, show_default); + for (const KeyValue<StringName, bool> &E : color_items) { + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_COLOR, E.key, E.value); ColorPickerButton *item_editor = memnew(ColorPickerButton); item_editor->set_h_size_flags(SIZE_EXPAND_FILL); item_control->add_child(item_editor); - if (E.get()) { - item_editor->set_pick_color(edited_theme->get_color(E.key(), edited_type)); - item_editor->connect("color_changed", callable_mp(this, &ThemeTypeEditor::_color_item_changed), varray(E.key())); + if (E.value) { + item_editor->set_pick_color(edited_theme->get_color(E.key, edited_type)); + item_editor->connect("color_changed", callable_mp(this, &ThemeTypeEditor::_color_item_changed).bind(E.key)); + item_editor->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(item_editor->get_picker())); } else { - item_editor->set_pick_color(Theme::get_default()->get_color(E.key(), edited_type)); + item_editor->set_pick_color(ThemeDB::get_singleton()->get_default_theme()->get_color(E.key, edited_type)); item_editor->set_disabled(true); } @@ -2481,13 +2512,13 @@ void ThemeTypeEditor::_update_type_items() { { for (int i = constant_items_list->get_child_count() - 1; i >= 0; i--) { Node *node = constant_items_list->get_child(i); - node->queue_delete(); + node->queue_free(); constant_items_list->remove_child(node); } - OrderedHashMap<StringName, bool> constant_items = _get_type_items(edited_type, &Theme::get_constant_list, show_default); - for (OrderedHashMap<StringName, bool>::Element E = constant_items.front(); E; E = E.next()) { - HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_CONSTANT, E.key(), E.get()); + HashMap<StringName, bool> constant_items = _get_type_items(edited_type, &Theme::get_constant_list, show_default); + for (const KeyValue<StringName, bool> &E : constant_items) { + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_CONSTANT, E.key, E.value); SpinBox *item_editor = memnew(SpinBox); item_editor->set_h_size_flags(SIZE_EXPAND_FILL); item_editor->set_min(-100000); @@ -2497,11 +2528,11 @@ void ThemeTypeEditor::_update_type_items() { item_editor->set_allow_greater(true); item_control->add_child(item_editor); - if (E.get()) { - item_editor->set_value(edited_theme->get_constant(E.key(), edited_type)); - item_editor->connect("value_changed", callable_mp(this, &ThemeTypeEditor::_constant_item_changed), varray(E.key())); + if (E.value) { + item_editor->set_value(edited_theme->get_constant(E.key, edited_type)); + item_editor->connect("value_changed", callable_mp(this, &ThemeTypeEditor::_constant_item_changed).bind(E.key)); } else { - item_editor->set_value(Theme::get_default()->get_constant(E.key(), edited_type)); + item_editor->set_value(ThemeDB::get_singleton()->get_default_theme()->get_constant(E.key, edited_type)); item_editor->set_editable(false); } @@ -2514,31 +2545,31 @@ void ThemeTypeEditor::_update_type_items() { { for (int i = font_items_list->get_child_count() - 1; i >= 0; i--) { Node *node = font_items_list->get_child(i); - node->queue_delete(); + node->queue_free(); font_items_list->remove_child(node); } - OrderedHashMap<StringName, bool> font_items = _get_type_items(edited_type, &Theme::get_font_list, show_default); - for (OrderedHashMap<StringName, bool>::Element E = font_items.front(); E; E = E.next()) { - HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_FONT, E.key(), E.get()); + HashMap<StringName, bool> font_items = _get_type_items(edited_type, &Theme::get_font_list, show_default); + for (const KeyValue<StringName, bool> &E : font_items) { + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_FONT, E.key, E.value); EditorResourcePicker *item_editor = memnew(EditorResourcePicker); item_editor->set_h_size_flags(SIZE_EXPAND_FILL); item_editor->set_base_type("Font"); item_control->add_child(item_editor); - if (E.get()) { - if (edited_theme->has_font(E.key(), edited_type)) { - item_editor->set_edited_resource(edited_theme->get_font(E.key(), edited_type)); + if (E.value) { + if (edited_theme->has_font(E.key, edited_type)) { + item_editor->set_edited_resource(edited_theme->get_font(E.key, edited_type)); } else { - item_editor->set_edited_resource(RES()); + item_editor->set_edited_resource(Ref<Resource>()); } item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item)); - item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_font_item_changed), varray(E.key())); + item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_font_item_changed).bind(E.key)); } else { - if (Theme::get_default()->has_font(E.key(), edited_type)) { - item_editor->set_edited_resource(Theme::get_default()->get_font(E.key(), edited_type)); + if (ThemeDB::get_singleton()->get_default_theme()->has_font(E.key, edited_type)) { + item_editor->set_edited_resource(ThemeDB::get_singleton()->get_default_theme()->get_font(E.key, edited_type)); } else { - item_editor->set_edited_resource(RES()); + item_editor->set_edited_resource(Ref<Resource>()); } item_editor->set_editable(false); } @@ -2552,13 +2583,13 @@ void ThemeTypeEditor::_update_type_items() { { for (int i = font_size_items_list->get_child_count() - 1; i >= 0; i--) { Node *node = font_size_items_list->get_child(i); - node->queue_delete(); + node->queue_free(); font_size_items_list->remove_child(node); } - OrderedHashMap<StringName, bool> font_size_items = _get_type_items(edited_type, &Theme::get_font_size_list, show_default); - for (OrderedHashMap<StringName, bool>::Element E = font_size_items.front(); E; E = E.next()) { - HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_FONT_SIZE, E.key(), E.get()); + HashMap<StringName, bool> font_size_items = _get_type_items(edited_type, &Theme::get_font_size_list, show_default); + for (const KeyValue<StringName, bool> &E : font_size_items) { + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_FONT_SIZE, E.key, E.value); SpinBox *item_editor = memnew(SpinBox); item_editor->set_h_size_flags(SIZE_EXPAND_FILL); item_editor->set_min(-100000); @@ -2568,11 +2599,11 @@ void ThemeTypeEditor::_update_type_items() { item_editor->set_allow_greater(true); item_control->add_child(item_editor); - if (E.get()) { - item_editor->set_value(edited_theme->get_font_size(E.key(), edited_type)); - item_editor->connect("value_changed", callable_mp(this, &ThemeTypeEditor::_font_size_item_changed), varray(E.key())); + if (E.value) { + item_editor->set_value(edited_theme->get_font_size(E.key, edited_type)); + item_editor->connect("value_changed", callable_mp(this, &ThemeTypeEditor::_font_size_item_changed).bind(E.key)); } else { - item_editor->set_value(Theme::get_default()->get_font_size(E.key(), edited_type)); + item_editor->set_value(ThemeDB::get_singleton()->get_default_theme()->get_font_size(E.key, edited_type)); item_editor->set_editable(false); } @@ -2585,31 +2616,31 @@ void ThemeTypeEditor::_update_type_items() { { for (int i = icon_items_list->get_child_count() - 1; i >= 0; i--) { Node *node = icon_items_list->get_child(i); - node->queue_delete(); + node->queue_free(); icon_items_list->remove_child(node); } - OrderedHashMap<StringName, bool> icon_items = _get_type_items(edited_type, &Theme::get_icon_list, show_default); - for (OrderedHashMap<StringName, bool>::Element E = icon_items.front(); E; E = E.next()) { - HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_ICON, E.key(), E.get()); + HashMap<StringName, bool> icon_items = _get_type_items(edited_type, &Theme::get_icon_list, show_default); + for (const KeyValue<StringName, bool> &E : icon_items) { + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_ICON, E.key, E.value); EditorResourcePicker *item_editor = memnew(EditorResourcePicker); item_editor->set_h_size_flags(SIZE_EXPAND_FILL); item_editor->set_base_type("Texture2D"); item_control->add_child(item_editor); - if (E.get()) { - if (edited_theme->has_icon(E.key(), edited_type)) { - item_editor->set_edited_resource(edited_theme->get_icon(E.key(), edited_type)); + if (E.value) { + if (edited_theme->has_icon(E.key, edited_type)) { + item_editor->set_edited_resource(edited_theme->get_icon(E.key, edited_type)); } else { - item_editor->set_edited_resource(RES()); + item_editor->set_edited_resource(Ref<Resource>()); } item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item)); - item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_icon_item_changed), varray(E.key())); + item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_icon_item_changed).bind(E.key)); } else { - if (Theme::get_default()->has_icon(E.key(), edited_type)) { - item_editor->set_edited_resource(Theme::get_default()->get_icon(E.key(), edited_type)); + if (ThemeDB::get_singleton()->get_default_theme()->has_icon(E.key, edited_type)) { + item_editor->set_edited_resource(ThemeDB::get_singleton()->get_default_theme()->get_icon(E.key, edited_type)); } else { - item_editor->set_edited_resource(RES()); + item_editor->set_edited_resource(Ref<Resource>()); } item_editor->set_editable(false); } @@ -2623,7 +2654,7 @@ void ThemeTypeEditor::_update_type_items() { { for (int i = stylebox_items_list->get_child_count() - 1; i >= 0; i--) { Node *node = stylebox_items_list->get_child(i); - node->queue_delete(); + node->queue_free(); stylebox_items_list->remove_child(node); } @@ -2639,7 +2670,7 @@ void ThemeTypeEditor::_update_type_items() { pin_leader_button->set_toggle_mode(true); pin_leader_button->set_pressed(true); pin_leader_button->set_icon(get_theme_icon(SNAME("Pin"), SNAME("EditorIcons"))); - pin_leader_button->set_tooltip(TTR("Unpin this StyleBox as a main style.")); + pin_leader_button->set_tooltip_text(TTR("Unpin this StyleBox as a main style.")); item_control->add_child(pin_leader_button); pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_on_unpin_leader_button_pressed)); @@ -2648,48 +2679,48 @@ void ThemeTypeEditor::_update_type_items() { if (edited_theme->has_stylebox(leading_stylebox.item_name, edited_type)) { item_editor->set_edited_resource(leading_stylebox.stylebox); } else { - item_editor->set_edited_resource(RES()); + item_editor->set_edited_resource(Ref<Resource>()); } item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item)); - item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_stylebox_item_changed), varray(leading_stylebox.item_name)); + item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_stylebox_item_changed).bind(leading_stylebox.item_name)); stylebox_items_list->add_child(item_control); stylebox_items_list->add_child(memnew(HSeparator)); } - OrderedHashMap<StringName, bool> stylebox_items = _get_type_items(edited_type, &Theme::get_stylebox_list, show_default); - for (OrderedHashMap<StringName, bool>::Element E = stylebox_items.front(); E; E = E.next()) { - if (leading_stylebox.pinned && leading_stylebox.item_name == E.key()) { + HashMap<StringName, bool> stylebox_items = _get_type_items(edited_type, &Theme::get_stylebox_list, show_default); + for (const KeyValue<StringName, bool> &E : stylebox_items) { + if (leading_stylebox.pinned && leading_stylebox.item_name == E.key) { continue; } - HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_STYLEBOX, E.key(), E.get()); + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_STYLEBOX, E.key, E.value); EditorResourcePicker *item_editor = memnew(EditorResourcePicker); item_editor->set_h_size_flags(SIZE_EXPAND_FILL); item_editor->set_stretch_ratio(1.5); item_editor->set_base_type("StyleBox"); - if (E.get()) { - if (edited_theme->has_stylebox(E.key(), edited_type)) { - item_editor->set_edited_resource(edited_theme->get_stylebox(E.key(), edited_type)); + if (E.value) { + if (edited_theme->has_stylebox(E.key, edited_type)) { + item_editor->set_edited_resource(edited_theme->get_stylebox(E.key, edited_type)); } else { - item_editor->set_edited_resource(RES()); + item_editor->set_edited_resource(Ref<Resource>()); } item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item)); - item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_stylebox_item_changed), varray(E.key())); + item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_stylebox_item_changed).bind(E.key)); Button *pin_leader_button = memnew(Button); pin_leader_button->set_flat(true); pin_leader_button->set_toggle_mode(true); pin_leader_button->set_icon(get_theme_icon(SNAME("Pin"), SNAME("EditorIcons"))); - pin_leader_button->set_tooltip(TTR("Pin this StyleBox as a main style. Editing its properties will update the same properties in all other StyleBoxes of this type.")); + pin_leader_button->set_tooltip_text(TTR("Pin this StyleBox as a main style. Editing its properties will update the same properties in all other StyleBoxes of this type.")); item_control->add_child(pin_leader_button); - pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_on_pin_leader_button_pressed), varray(item_editor, E.key())); + pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_on_pin_leader_button_pressed).bind(item_editor, E.key)); } else { - if (Theme::get_default()->has_stylebox(E.key(), edited_type)) { - item_editor->set_edited_resource(Theme::get_default()->get_stylebox(E.key(), edited_type)); + if (ThemeDB::get_singleton()->get_default_theme()->has_stylebox(E.key, edited_type)) { + item_editor->set_edited_resource(ThemeDB::get_singleton()->get_default_theme()->get_stylebox(E.key, edited_type)); } else { - item_editor->set_edited_resource(RES()); + item_editor->set_edited_resource(Ref<Resource>()); } item_editor->set_editable(false); } @@ -2723,7 +2754,7 @@ void ThemeTypeEditor::_list_type_selected(int p_index) { void ThemeTypeEditor::_add_type_button_cbk() { add_type_mode = ADD_THEME_TYPE; add_type_dialog->set_title(TTR("Add Item Type")); - add_type_dialog->get_ok_button()->set_text(TTR("Add Type")); + add_type_dialog->set_ok_button_text(TTR("Add Type")); add_type_dialog->set_include_own_types(false); add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE); } @@ -2742,62 +2773,62 @@ void ThemeTypeEditor::_add_default_type_items() { { names.clear(); - Theme::get_default()->get_icon_list(default_type, &names); + ThemeDB::get_singleton()->get_default_theme()->get_icon_list(default_type, &names); for (const StringName &E : names) { if (!new_snapshot->has_icon(E, edited_type)) { - new_snapshot->set_icon(E, edited_type, Theme::get_default()->get_icon(E, edited_type)); + new_snapshot->set_icon(E, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_icon(E, edited_type)); } } } { names.clear(); - Theme::get_default()->get_stylebox_list(default_type, &names); + ThemeDB::get_singleton()->get_default_theme()->get_stylebox_list(default_type, &names); for (const StringName &E : names) { if (!new_snapshot->has_stylebox(E, edited_type)) { - new_snapshot->set_stylebox(E, edited_type, Theme::get_default()->get_stylebox(E, edited_type)); + new_snapshot->set_stylebox(E, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_stylebox(E, edited_type)); } } } { names.clear(); - Theme::get_default()->get_font_list(default_type, &names); + ThemeDB::get_singleton()->get_default_theme()->get_font_list(default_type, &names); for (const StringName &E : names) { if (!new_snapshot->has_font(E, edited_type)) { - new_snapshot->set_font(E, edited_type, Theme::get_default()->get_font(E, edited_type)); + new_snapshot->set_font(E, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_font(E, edited_type)); } } } { names.clear(); - Theme::get_default()->get_font_size_list(default_type, &names); + ThemeDB::get_singleton()->get_default_theme()->get_font_size_list(default_type, &names); for (const StringName &E : names) { if (!new_snapshot->has_font_size(E, edited_type)) { - new_snapshot->set_font_size(E, edited_type, Theme::get_default()->get_font_size(E, edited_type)); + new_snapshot->set_font_size(E, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_font_size(E, edited_type)); } } } { names.clear(); - Theme::get_default()->get_color_list(default_type, &names); + ThemeDB::get_singleton()->get_default_theme()->get_color_list(default_type, &names); for (const StringName &E : names) { if (!new_snapshot->has_color(E, edited_type)) { - new_snapshot->set_color(E, edited_type, Theme::get_default()->get_color(E, edited_type)); + new_snapshot->set_color(E, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_color(E, edited_type)); } } } { names.clear(); - Theme::get_default()->get_constant_list(default_type, &names); + ThemeDB::get_singleton()->get_default_theme()->get_constant_list(default_type, &names); for (const StringName &E : names) { if (!new_snapshot->has_constant(E, edited_type)) { - new_snapshot->set_constant(E, edited_type, Theme::get_default()->get_constant(E, edited_type)); + new_snapshot->set_constant(E, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_constant(E, edited_type)); } } } updating = false; - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Override All Default Theme Items")); ur->add_do_method(*edited_theme, "merge_with", new_snapshot); @@ -2817,7 +2848,7 @@ void ThemeTypeEditor::_item_add_cbk(int p_data_type, Control *p_control) { } String item_name = le->get_text().strip_edges(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Add Theme Item")); switch (p_data_type) { @@ -2862,16 +2893,16 @@ void ThemeTypeEditor::_item_add_lineedit_cbk(String p_value, int p_data_type, Co } void ThemeTypeEditor::_item_override_cbk(int p_data_type, String p_item_name) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Override Theme Item")); switch (p_data_type) { case Theme::DATA_TYPE_COLOR: { - ur->add_do_method(*edited_theme, "set_color", p_item_name, edited_type, Theme::get_default()->get_color(p_item_name, edited_type)); + ur->add_do_method(*edited_theme, "set_color", p_item_name, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_color(p_item_name, edited_type)); ur->add_undo_method(*edited_theme, "clear_color", p_item_name, edited_type); } break; case Theme::DATA_TYPE_CONSTANT: { - ur->add_do_method(*edited_theme, "set_constant", p_item_name, edited_type, Theme::get_default()->get_constant(p_item_name, edited_type)); + ur->add_do_method(*edited_theme, "set_constant", p_item_name, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_constant(p_item_name, edited_type)); ur->add_undo_method(*edited_theme, "clear_constant", p_item_name, edited_type); } break; case Theme::DATA_TYPE_FONT: { @@ -2879,7 +2910,7 @@ void ThemeTypeEditor::_item_override_cbk(int p_data_type, String p_item_name) { ur->add_undo_method(*edited_theme, "clear_font", p_item_name, edited_type); } break; case Theme::DATA_TYPE_FONT_SIZE: { - ur->add_do_method(*edited_theme, "set_font_size", p_item_name, edited_type, Theme::get_default()->get_font_size(p_item_name, edited_type)); + ur->add_do_method(*edited_theme, "set_font_size", p_item_name, edited_type, ThemeDB::get_singleton()->get_default_theme()->get_font_size(p_item_name, edited_type)); ur->add_undo_method(*edited_theme, "clear_font_size", p_item_name, edited_type); } break; case Theme::DATA_TYPE_ICON: { @@ -2901,7 +2932,7 @@ void ThemeTypeEditor::_item_override_cbk(int p_data_type, String p_item_name) { } void ThemeTypeEditor::_item_remove_cbk(int p_data_type, String p_item_name) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Remove Theme Item")); switch (p_data_type) { @@ -2921,10 +2952,6 @@ void ThemeTypeEditor::_item_remove_cbk(int p_data_type, String p_item_name) { ur->add_undo_method(*edited_theme, "set_font", p_item_name, edited_type, Ref<Font>()); } } break; - case Theme::DATA_TYPE_FONT_SIZE: { - ur->add_do_method(*edited_theme, "clear_font_size", p_item_name, edited_type); - ur->add_undo_method(*edited_theme, "set_font_size", p_item_name, edited_type, edited_theme->get_font_size(p_item_name, edited_type)); - } break; case Theme::DATA_TYPE_ICON: { ur->add_do_method(*edited_theme, "clear_icon", p_item_name, edited_type); if (edited_theme->has_icon(p_item_name, edited_type)) { @@ -2979,7 +3006,7 @@ void ThemeTypeEditor::_item_rename_confirmed(int p_data_type, String p_item_name return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Rename Theme Item")); switch (p_data_type) { @@ -3035,7 +3062,7 @@ void ThemeTypeEditor::_item_rename_canceled(int p_data_type, String p_item_name, } void ThemeTypeEditor::_color_item_changed(Color p_value, String p_item_name) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Color Item in Theme"), UndoRedo::MERGE_ENDS); ur->add_do_method(*edited_theme, "set_color", p_item_name, edited_type, p_value); ur->add_undo_method(*edited_theme, "set_color", p_item_name, edited_type, edited_theme->get_color(p_item_name, edited_type)); @@ -3043,7 +3070,7 @@ void ThemeTypeEditor::_color_item_changed(Color p_value, String p_item_name) { } void ThemeTypeEditor::_constant_item_changed(float p_value, String p_item_name) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Constant Item in Theme")); ur->add_do_method(*edited_theme, "set_constant", p_item_name, edited_type, p_value); ur->add_undo_method(*edited_theme, "set_constant", p_item_name, edited_type, edited_theme->get_constant(p_item_name, edited_type)); @@ -3051,19 +3078,19 @@ void ThemeTypeEditor::_constant_item_changed(float p_value, String p_item_name) } void ThemeTypeEditor::_font_size_item_changed(float p_value, String p_item_name) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Font Size Item in Theme")); ur->add_do_method(*edited_theme, "set_font_size", p_item_name, edited_type, p_value); ur->add_undo_method(*edited_theme, "set_font_size", p_item_name, edited_type, edited_theme->get_font_size(p_item_name, edited_type)); ur->commit_action(); } -void ThemeTypeEditor::_edit_resource_item(RES p_resource, bool p_edit) { +void ThemeTypeEditor::_edit_resource_item(Ref<Resource> p_resource, bool p_edit) { EditorNode::get_singleton()->edit_resource(p_resource); } void ThemeTypeEditor::_font_item_changed(Ref<Font> p_value, String p_item_name) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Font Item in Theme")); ur->add_do_method(*edited_theme, "set_font", p_item_name, edited_type, p_value.is_valid() ? p_value : Ref<Font>()); @@ -3080,7 +3107,7 @@ void ThemeTypeEditor::_font_item_changed(Ref<Font> p_value, String p_item_name) } void ThemeTypeEditor::_icon_item_changed(Ref<Texture2D> p_value, String p_item_name) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Icon Item in Theme")); ur->add_do_method(*edited_theme, "set_icon", p_item_name, edited_type, p_value.is_valid() ? p_value : Ref<Texture2D>()); @@ -3097,7 +3124,7 @@ void ThemeTypeEditor::_icon_item_changed(Ref<Texture2D> p_value, String p_item_n } void ThemeTypeEditor::_stylebox_item_changed(Ref<StyleBox> p_value, String p_item_name) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Stylebox Item in Theme")); ur->add_do_method(*edited_theme, "set_stylebox", p_item_name, edited_type, p_value.is_valid() ? p_value : Ref<StyleBox>()); @@ -3124,7 +3151,7 @@ void ThemeTypeEditor::_change_pinned_stylebox() { Ref<StyleBox> new_stylebox = edited_theme->get_stylebox(leading_stylebox.item_name, edited_type); leading_stylebox.stylebox = new_stylebox; - leading_stylebox.ref_stylebox = (new_stylebox.is_valid() ? new_stylebox->duplicate() : RES()); + leading_stylebox.ref_stylebox = (new_stylebox.is_valid() ? new_stylebox->duplicate() : Ref<Resource>()); if (leading_stylebox.stylebox.is_valid()) { new_stylebox->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); @@ -3140,7 +3167,7 @@ void ThemeTypeEditor::_on_pin_leader_button_pressed(Control *p_editor, String p_ stylebox = Object::cast_to<EditorResourcePicker>(p_editor)->get_edited_resource(); } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Pin Stylebox")); ur->add_do_method(this, "_pin_leading_stylebox", p_item_name, stylebox); @@ -3162,7 +3189,7 @@ void ThemeTypeEditor::_pin_leading_stylebox(String p_item_name, Ref<StyleBox> p_ leader.pinned = true; leader.item_name = p_item_name; leader.stylebox = p_stylebox; - leader.ref_stylebox = (p_stylebox.is_valid() ? p_stylebox->duplicate() : RES()); + leader.ref_stylebox = (p_stylebox.is_valid() ? p_stylebox->duplicate() : Ref<Resource>()); leading_stylebox = leader; if (p_stylebox.is_valid()) { @@ -3173,7 +3200,7 @@ void ThemeTypeEditor::_pin_leading_stylebox(String p_item_name, Ref<StyleBox> p_ } void ThemeTypeEditor::_on_unpin_leader_button_pressed() { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Unpin Stylebox")); ur->add_do_method(this, "_unpin_leading_stylebox"); ur->add_undo_method(this, "_pin_leading_stylebox", leading_stylebox.item_name, leading_stylebox.stylebox); @@ -3204,11 +3231,13 @@ void ThemeTypeEditor::_update_stylebox_from_leading() { edited_theme->get_stylebox_list(edited_type, &names); List<Ref<StyleBox>> styleboxes; for (const StringName &E : names) { - if (E == leading_stylebox.item_name) { + Ref<StyleBox> sb = edited_theme->get_stylebox(E, edited_type); + + // Avoid itself, stylebox can be shared between items. + if (sb == leading_stylebox.stylebox) { continue; } - Ref<StyleBox> sb = edited_theme->get_stylebox(E, edited_type); if (sb->get_class() == leading_stylebox.stylebox->get_class()) { styleboxes.push_back(sb); } @@ -3240,7 +3269,7 @@ void ThemeTypeEditor::_update_stylebox_from_leading() { } void ThemeTypeEditor::_type_variation_changed(const String p_value) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); ur->create_action(TTR("Set Theme Type Variation")); if (p_value.is_empty()) { @@ -3261,7 +3290,7 @@ void ThemeTypeEditor::_type_variation_changed(const String p_value) { void ThemeTypeEditor::_add_type_variation_cbk() { add_type_mode = ADD_VARIATION_BASE; add_type_dialog->set_title(TTR("Set Variation Base Type")); - add_type_dialog->get_ok_button()->set_text(TTR("Set Base Type")); + add_type_dialog->set_ok_button_text(TTR("Set Base Type")); add_type_dialog->set_include_own_types(true); add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE); } @@ -3288,9 +3317,6 @@ void ThemeTypeEditor::_notification(int p_what) { data_type_tabs->set_tab_icon(5, get_theme_icon(SNAME("StyleBoxFlat"), SNAME("EditorIcons"))); data_type_tabs->set_tab_icon(6, get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); - data_type_tabs->add_theme_style_override("tab_selected", get_theme_stylebox(SNAME("tab_selected_odd"), SNAME("TabContainer"))); - data_type_tabs->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel_odd"), SNAME("TabContainer"))); - type_variation_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); } break; } @@ -3309,8 +3335,10 @@ void ThemeTypeEditor::set_edited_theme(const Ref<Theme> &p_theme) { } edited_theme = p_theme; - edited_theme->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced)); - _update_type_list(); + if (edited_theme.is_valid()) { + edited_theme->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced)); + _update_type_list(); + } add_type_dialog->set_edited_theme(edited_theme); } @@ -3359,11 +3387,12 @@ ThemeTypeEditor::ThemeTypeEditor() { theme_type_list = memnew(OptionButton); theme_type_list->set_h_size_flags(SIZE_EXPAND_FILL); + theme_type_list->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); type_list_hb->add_child(theme_type_list); theme_type_list->connect("item_selected", callable_mp(this, &ThemeTypeEditor::_list_type_selected)); add_type_button = memnew(Button); - add_type_button->set_tooltip(TTR("Add a type from a list of available types or create a new one.")); + add_type_button->set_tooltip_text(TTR("Add a type from a list of available types or create a new one.")); type_list_hb->add_child(add_type_button); add_type_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_type_button_cbk)); @@ -3373,7 +3402,7 @@ ThemeTypeEditor::ThemeTypeEditor() { show_default_items_button = memnew(CheckButton); show_default_items_button->set_h_size_flags(SIZE_EXPAND_FILL); show_default_items_button->set_text(TTR("Show Default")); - show_default_items_button->set_tooltip(TTR("Show default type items alongside items that have been overridden.")); + show_default_items_button->set_tooltip_text(TTR("Show default type items alongside items that have been overridden.")); show_default_items_button->set_pressed(true); type_controls->add_child(show_default_items_button); show_default_items_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_update_type_items)); @@ -3381,14 +3410,16 @@ ThemeTypeEditor::ThemeTypeEditor() { Button *add_default_items_button = memnew(Button); add_default_items_button->set_h_size_flags(SIZE_EXPAND_FILL); add_default_items_button->set_text(TTR("Override All")); - add_default_items_button->set_tooltip(TTR("Override all default type items.")); + add_default_items_button->set_tooltip_text(TTR("Override all default type items.")); type_controls->add_child(add_default_items_button); add_default_items_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_default_type_items)); data_type_tabs = memnew(TabContainer); + data_type_tabs->set_tab_alignment(TabBar::ALIGNMENT_CENTER); main_vb->add_child(data_type_tabs); data_type_tabs->set_v_size_flags(SIZE_EXPAND_FILL); data_type_tabs->set_use_hidden_tabs_for_min_size(true); + data_type_tabs->set_theme_type_variation("TabContainerOdd"); color_items_list = _create_item_list(Theme::DATA_TYPE_COLOR); constant_items_list = _create_item_list(Theme::DATA_TYPE_CONSTANT); @@ -3426,13 +3457,13 @@ ThemeTypeEditor::ThemeTypeEditor() { type_variation_edit->connect("focus_exited", callable_mp(this, &ThemeTypeEditor::_update_type_items)); type_variation_button = memnew(Button); type_variation_hb->add_child(type_variation_button); - type_variation_button->set_tooltip(TTR("Select the variation base type from a list of available types.")); + type_variation_button->set_tooltip_text(TTR("Select the variation base type from a list of available types.")); type_variation_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_type_variation_cbk)); type_variation_locked = memnew(Label); type_variation_vb->add_child(type_variation_locked); type_variation_locked->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); - type_variation_locked->set_autowrap_mode(Label::AUTOWRAP_WORD); + type_variation_locked->set_autowrap_mode(TextServer::AUTOWRAP_WORD); type_variation_locked->set_text(TTR("A type associated with a built-in class cannot be marked as a variation of another type.")); type_variation_locked->hide(); @@ -3447,6 +3478,8 @@ ThemeTypeEditor::ThemeTypeEditor() { add_child(update_debounce_timer); } +/////////////////////// + void ThemeEditor::edit(const Ref<Theme> &p_theme) { if (theme == p_theme) { return; @@ -3465,7 +3498,9 @@ void ThemeEditor::edit(const Ref<Theme> &p_theme) { preview_tab->set_preview_theme(p_theme); } - theme_name->set_text(TTR("Theme:") + " " + theme->get_path().get_file()); + if (theme.is_valid()) { + theme_name->set_text(TTR("Theme:") + " " + theme->get_path().get_file()); + } } Ref<Theme> ThemeEditor::get_edited_theme() { @@ -3497,8 +3532,8 @@ void ThemeEditor::_preview_scene_dialog_cbk(const String &p_path) { } _add_preview_tab(preview_tab, p_path.get_file(), get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons"))); - preview_tab->connect("scene_invalidated", callable_mp(this, &ThemeEditor::_remove_preview_tab_invalid), varray(preview_tab)); - preview_tab->connect("scene_reloaded", callable_mp(this, &ThemeEditor::_update_preview_tab), varray(preview_tab)); + preview_tab->connect("scene_invalidated", callable_mp(this, &ThemeEditor::_remove_preview_tab_invalid).bind(preview_tab)); + preview_tab->connect("scene_reloaded", callable_mp(this, &ThemeEditor::_update_preview_tab).bind(preview_tab)); } void ThemeEditor::_add_preview_tab(ThemeEditorPreview *p_preview_tab, const String &p_preview_name, const Ref<Texture2D> &p_icon) { @@ -3506,7 +3541,7 @@ void ThemeEditor::_add_preview_tab(ThemeEditorPreview *p_preview_tab, const Stri preview_tabs->add_tab(p_preview_name, p_icon); preview_tabs_content->add_child(p_preview_tab); - preview_tabs->set_tab_right_button(preview_tabs->get_tab_count() - 1, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("close"), SNAME("TabBar"))); + preview_tabs->set_tab_button_icon(preview_tabs->get_tab_count() - 1, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("close"), SNAME("TabBar"))); p_preview_tab->connect("control_picked", callable_mp(this, &ThemeEditor::_preview_control_picked)); preview_tabs->set_current_tab(preview_tabs->get_tab_count() - 1); @@ -3571,7 +3606,7 @@ void ThemeEditor::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { preview_tabs->add_theme_style_override("tab_selected", get_theme_stylebox(SNAME("ThemeEditorPreviewFG"), SNAME("EditorStyles"))); preview_tabs->add_theme_style_override("tab_unselected", get_theme_stylebox(SNAME("ThemeEditorPreviewBG"), SNAME("EditorStyles"))); - preview_tabs_content->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel_odd"), SNAME("TabContainer"))); + preview_tabs_content->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainerOdd"))); add_preview_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); } break; @@ -3592,20 +3627,20 @@ ThemeEditor::ThemeEditor() { Button *theme_save_button = memnew(Button); theme_save_button->set_text(TTR("Save")); theme_save_button->set_flat(true); - theme_save_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_save_button_cbk), varray(false)); + theme_save_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_save_button_cbk).bind(false)); top_menu->add_child(theme_save_button); Button *theme_save_as_button = memnew(Button); theme_save_as_button->set_text(TTR("Save As...")); theme_save_as_button->set_flat(true); - theme_save_as_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_save_button_cbk), varray(true)); + theme_save_as_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_save_button_cbk).bind(true)); top_menu->add_child(theme_save_as_button); top_menu->add_child(memnew(VSeparator)); Button *theme_edit_button = memnew(Button); theme_edit_button->set_text(TTR("Manage Items...")); - theme_edit_button->set_tooltip(TTR("Add, remove, organize and import Theme items.")); + theme_edit_button->set_tooltip_text(TTR("Add, remove, organize and import Theme items.")); theme_edit_button->set_flat(true); theme_edit_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_edit_button_cbk)); top_menu->add_child(theme_edit_button); @@ -3633,11 +3668,10 @@ ThemeEditor::ThemeEditor() { preview_tabs_vb->add_child(preview_tabs_content); preview_tabs = memnew(TabBar); - preview_tabs->set_tab_alignment(TabBar::ALIGNMENT_LEFT); preview_tabs->set_h_size_flags(SIZE_EXPAND_FILL); preview_tabbar_hb->add_child(preview_tabs); preview_tabs->connect("tab_changed", callable_mp(this, &ThemeEditor::_change_preview_tab)); - preview_tabs->connect("tab_rmb_clicked", callable_mp(this, &ThemeEditor::_remove_preview_tab)); + preview_tabs->connect("tab_button_pressed", callable_mp(this, &ThemeEditor::_remove_preview_tab)); HBoxContainer *add_preview_button_hb = memnew(HBoxContainer); preview_tabbar_hb->add_child(add_preview_button_hb); @@ -3657,7 +3691,7 @@ ThemeEditor::ThemeEditor() { List<String> ext; ResourceLoader::get_recognized_extensions_for_type("PackedScene", &ext); for (const String &E : ext) { - preview_scene_dialog->add_filter("*." + E + "; Scene"); + preview_scene_dialog->add_filter("*." + E, TTR("Scene")); } main_hs->add_child(preview_scene_dialog); preview_scene_dialog->connect("file_selected", callable_mp(this, &ThemeEditor::_preview_scene_dialog_cbk)); @@ -3666,105 +3700,37 @@ ThemeEditor::ThemeEditor() { theme_type_editor->set_custom_minimum_size(Size2(280, 0) * EDSCALE); } +/////////////////////// + void ThemeEditorPlugin::edit(Object *p_node) { if (Object::cast_to<Theme>(p_node)) { theme_editor->edit(Object::cast_to<Theme>(p_node)); - } else if (Object::cast_to<Font>(p_node) || Object::cast_to<StyleBox>(p_node) || Object::cast_to<Texture2D>(p_node)) { - // Do nothing, keep editing the existing theme. } else { theme_editor->edit(Ref<Theme>()); } } bool ThemeEditorPlugin::handles(Object *p_node) const { - if (Object::cast_to<Theme>(p_node)) { - return true; - } - - Ref<Theme> edited_theme = theme_editor->get_edited_theme(); - if (edited_theme.is_null()) { - return false; - } - - // If we are editing a theme already and this particular resource happens to belong to it, - // then we just keep editing it, despite not being able to directly handle it. - // This only goes one layer deep, but if required this can be extended to support, say, FontData inside of Font. - bool belongs_to_theme = false; - - if (Object::cast_to<Font>(p_node)) { - Ref<Font> font_item = Object::cast_to<Font>(p_node); - List<StringName> types; - List<StringName> names; - - edited_theme->get_font_type_list(&types); - for (const StringName &E : types) { - names.clear(); - edited_theme->get_font_list(E, &names); - - for (const StringName &F : names) { - if (font_item == edited_theme->get_font(F, E)) { - belongs_to_theme = true; - break; - } - } - } - } else if (Object::cast_to<StyleBox>(p_node)) { - Ref<StyleBox> stylebox_item = Object::cast_to<StyleBox>(p_node); - List<StringName> types; - List<StringName> names; - - edited_theme->get_stylebox_type_list(&types); - for (const StringName &E : types) { - names.clear(); - edited_theme->get_stylebox_list(E, &names); - - for (const StringName &F : names) { - if (stylebox_item == edited_theme->get_stylebox(F, E)) { - belongs_to_theme = true; - break; - } - } - } - } else if (Object::cast_to<Texture2D>(p_node)) { - Ref<Texture2D> icon_item = Object::cast_to<Texture2D>(p_node); - List<StringName> types; - List<StringName> names; - - edited_theme->get_icon_type_list(&types); - for (const StringName &E : types) { - names.clear(); - edited_theme->get_icon_list(E, &names); - - for (const StringName &F : names) { - if (icon_item == edited_theme->get_icon(F, E)) { - belongs_to_theme = true; - break; - } - } - } - } - - return belongs_to_theme; + return Object::cast_to<Theme>(p_node) != nullptr; } void ThemeEditorPlugin::make_visible(bool p_visible) { if (p_visible) { button->show(); - editor->make_bottom_panel_item_visible(theme_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(theme_editor); } else { if (theme_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); } button->hide(); } } -ThemeEditorPlugin::ThemeEditorPlugin(EditorNode *p_node) { - editor = p_node; +ThemeEditorPlugin::ThemeEditorPlugin() { theme_editor = memnew(ThemeEditor); theme_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE); - button = editor->add_bottom_panel_item(TTR("Theme"), theme_editor); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Theme"), theme_editor); button->hide(); } diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index 4c6b16a68c..c8c944118c 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -1,46 +1,52 @@ -/*************************************************************************/ -/* theme_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* theme_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 THEME_EDITOR_PLUGIN_H #define THEME_EDITOR_PLUGIN_H +#include "editor/editor_plugin.h" +#include "editor/plugins/theme_editor_preview.h" +#include "scene/gui/check_button.h" #include "scene/gui/dialogs.h" +#include "scene/gui/item_list.h" #include "scene/gui/margin_container.h" #include "scene/gui/option_button.h" #include "scene/gui/scroll_container.h" #include "scene/gui/tab_bar.h" #include "scene/gui/texture_rect.h" +#include "scene/gui/tree.h" #include "scene/resources/theme.h" -#include "theme_editor_preview.h" -#include "editor/editor_node.h" +class EditorFileDialog; +class PanelContainer; +class TabContainer; class ThemeItemImportTree : public VBoxContainer { GDCLASS(ThemeItemImportTree, VBoxContainer); @@ -69,11 +75,11 @@ class ThemeItemImportTree : public VBoxContainer { SELECT_IMPORT_FULL, }; - Map<ThemeItem, ItemCheckedState> selected_items; + RBMap<ThemeItem, ItemCheckedState> selected_items; - LineEdit *import_items_filter; + LineEdit *import_items_filter = nullptr; - Tree *import_items_tree; + Tree *import_items_tree = nullptr; List<TreeItem *> tree_color_items; List<TreeItem *> tree_constant_items; List<TreeItem *> tree_font_items; @@ -88,57 +94,57 @@ class ThemeItemImportTree : public VBoxContainer { IMPORT_ITEM_DATA = 2, }; - TextureRect *select_colors_icon; - Label *select_colors_label; - Button *select_all_colors_button; - Button *select_full_colors_button; - Button *deselect_all_colors_button; - Label *total_selected_colors_label; - - TextureRect *select_constants_icon; - Label *select_constants_label; - Button *select_all_constants_button; - Button *select_full_constants_button; - Button *deselect_all_constants_button; - Label *total_selected_constants_label; - - TextureRect *select_fonts_icon; - Label *select_fonts_label; - Button *select_all_fonts_button; - Button *select_full_fonts_button; - Button *deselect_all_fonts_button; - Label *total_selected_fonts_label; - - TextureRect *select_font_sizes_icon; - Label *select_font_sizes_label; - Button *select_all_font_sizes_button; - Button *select_full_font_sizes_button; - Button *deselect_all_font_sizes_button; - Label *total_selected_font_sizes_label; - - TextureRect *select_icons_icon; - Label *select_icons_label; - Button *select_all_icons_button; - Button *select_full_icons_button; - Button *deselect_all_icons_button; - Label *total_selected_icons_label; - - TextureRect *select_styleboxes_icon; - Label *select_styleboxes_label; - Button *select_all_styleboxes_button; - Button *select_full_styleboxes_button; - Button *deselect_all_styleboxes_button; - Label *total_selected_styleboxes_label; - - HBoxContainer *select_icons_warning_hb; - TextureRect *select_icons_warning_icon; - Label *select_icons_warning; - - Button *import_collapse_types_button; - Button *import_expand_types_button; - Button *import_select_all_button; - Button *import_select_full_button; - Button *import_deselect_all_button; + TextureRect *select_colors_icon = nullptr; + Label *select_colors_label = nullptr; + Button *select_all_colors_button = nullptr; + Button *select_full_colors_button = nullptr; + Button *deselect_all_colors_button = nullptr; + Label *total_selected_colors_label = nullptr; + + TextureRect *select_constants_icon = nullptr; + Label *select_constants_label = nullptr; + Button *select_all_constants_button = nullptr; + Button *select_full_constants_button = nullptr; + Button *deselect_all_constants_button = nullptr; + Label *total_selected_constants_label = nullptr; + + TextureRect *select_fonts_icon = nullptr; + Label *select_fonts_label = nullptr; + Button *select_all_fonts_button = nullptr; + Button *select_full_fonts_button = nullptr; + Button *deselect_all_fonts_button = nullptr; + Label *total_selected_fonts_label = nullptr; + + TextureRect *select_font_sizes_icon = nullptr; + Label *select_font_sizes_label = nullptr; + Button *select_all_font_sizes_button = nullptr; + Button *select_full_font_sizes_button = nullptr; + Button *deselect_all_font_sizes_button = nullptr; + Label *total_selected_font_sizes_label = nullptr; + + TextureRect *select_icons_icon = nullptr; + Label *select_icons_label = nullptr; + Button *select_all_icons_button = nullptr; + Button *select_full_icons_button = nullptr; + Button *deselect_all_icons_button = nullptr; + Label *total_selected_icons_label = nullptr; + + TextureRect *select_styleboxes_icon = nullptr; + Label *select_styleboxes_label = nullptr; + Button *select_all_styleboxes_button = nullptr; + Button *select_full_styleboxes_button = nullptr; + Button *deselect_all_styleboxes_button = nullptr; + Label *total_selected_styleboxes_label = nullptr; + + HBoxContainer *select_icons_warning_hb = nullptr; + TextureRect *select_icons_warning_icon = nullptr; + Label *select_icons_warning = nullptr; + + Button *import_collapse_types_button = nullptr; + Button *import_expand_types_button = nullptr; + Button *import_select_all_button = nullptr; + Button *import_select_full_button = nullptr; + Button *import_deselect_all_button = nullptr; void _update_items_tree(); void _toggle_type_items(bool p_collapse); @@ -149,9 +155,9 @@ class ThemeItemImportTree : public VBoxContainer { void _update_total_selected(Theme::DataType p_data_type); void _tree_item_edited(); + void _check_propagated_to_tree_item(Object *p_obj, int p_column); void _select_all_subitems(TreeItem *p_root_item, bool p_select_with_data); void _deselect_all_subitems(TreeItem *p_root_item, bool p_deselect_completely); - void _update_parent_items(TreeItem *p_root_item); void _select_all_items_pressed(); void _select_full_items_pressed(); @@ -182,27 +188,32 @@ class ThemeTypeEditor; class ThemeItemEditorDialog : public AcceptDialog { GDCLASS(ThemeItemEditorDialog, AcceptDialog); - ThemeTypeEditor *theme_type_editor; + ThemeTypeEditor *theme_type_editor = nullptr; Ref<Theme> edited_theme; - TabContainer *tc; + TabContainer *tc = nullptr; - ItemList *edit_type_list; - LineEdit *edit_add_type_value; + enum TypesTreeAction { + TYPES_TREE_REMOVE_ITEM, + }; + + Tree *edit_type_list = nullptr; + LineEdit *edit_add_type_value = nullptr; + Button *edit_add_type_button = nullptr; String edited_item_type; - Button *edit_items_add_color; - Button *edit_items_add_constant; - Button *edit_items_add_font; - Button *edit_items_add_font_size; - Button *edit_items_add_icon; - Button *edit_items_add_stylebox; - Button *edit_items_remove_class; - Button *edit_items_remove_custom; - Button *edit_items_remove_all; - Tree *edit_items_tree; - Label *edit_items_message; + Button *edit_items_add_color = nullptr; + Button *edit_items_add_constant = nullptr; + Button *edit_items_add_font = nullptr; + Button *edit_items_add_font_size = nullptr; + Button *edit_items_add_icon = nullptr; + Button *edit_items_add_stylebox = nullptr; + Button *edit_items_remove_class = nullptr; + Button *edit_items_remove_custom = nullptr; + Button *edit_items_remove_all = nullptr; + Tree *edit_items_tree = nullptr; + Label *edit_items_message = nullptr; enum ItemsTreeAction { ITEMS_TREE_RENAME_ITEM, @@ -210,10 +221,10 @@ class ThemeItemEditorDialog : public AcceptDialog { ITEMS_TREE_REMOVE_DATA_TYPE, }; - ConfirmationDialog *edit_theme_item_dialog; - VBoxContainer *edit_theme_item_old_vb; - Label *theme_item_old_name; - LineEdit *theme_item_name; + ConfirmationDialog *edit_theme_item_dialog = nullptr; + VBoxContainer *edit_theme_item_old_vb = nullptr; + Label *theme_item_old_name = nullptr; + LineEdit *theme_item_name = nullptr; enum ItemPopupMode { CREATE_THEME_ITEM, @@ -225,28 +236,30 @@ class ThemeItemEditorDialog : public AcceptDialog { String edit_item_old_name; Theme::DataType edit_item_data_type = Theme::DATA_TYPE_MAX; - ThemeItemImportTree *import_default_theme_items; - ThemeItemImportTree *import_editor_theme_items; - ThemeItemImportTree *import_other_theme_items; + ThemeItemImportTree *import_default_theme_items = nullptr; + ThemeItemImportTree *import_editor_theme_items = nullptr; + ThemeItemImportTree *import_other_theme_items = nullptr; - LineEdit *import_another_theme_value; - Button *import_another_theme_button; - EditorFileDialog *import_another_theme_dialog; + LineEdit *import_another_theme_value = nullptr; + Button *import_another_theme_button = nullptr; + EditorFileDialog *import_another_theme_dialog = nullptr; - ConfirmationDialog *confirm_closing_dialog; + ConfirmationDialog *confirm_closing_dialog = nullptr; void ok_pressed() override; void _close_dialog(); void _dialog_about_to_show(); void _update_edit_types(); - void _edited_type_selected(int p_item_idx); + void _edited_type_selected(); + void _edited_type_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button); void _update_edit_item_tree(String p_item_type); - void _item_tree_button_pressed(Object *p_item, int p_column, int p_id); + void _item_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button); void _add_theme_type(const String &p_new_text); void _add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type); + void _remove_theme_type(const String &p_theme_type); void _remove_data_type_items(Theme::DataType p_data_type, String p_item_type); void _remove_class_items(); void _remove_custom_items(); @@ -278,9 +291,9 @@ class ThemeTypeDialog : public ConfirmationDialog { String pre_submitted_value; - LineEdit *add_type_filter; - ItemList *add_type_options; - ConfirmationDialog *add_type_confirmation; + LineEdit *add_type_filter = nullptr; + ItemList *add_type_options = nullptr; + ConfirmationDialog *add_type_confirmation = nullptr; void _dialog_about_to_show(); void ok_pressed() override; @@ -322,22 +335,22 @@ class ThemeTypeEditor : public MarginContainer { LeadingStylebox leading_stylebox; - OptionButton *theme_type_list; - Button *add_type_button; + OptionButton *theme_type_list = nullptr; + Button *add_type_button = nullptr; - CheckButton *show_default_items_button; + CheckButton *show_default_items_button = nullptr; - TabContainer *data_type_tabs; - VBoxContainer *color_items_list; - VBoxContainer *constant_items_list; - VBoxContainer *font_items_list; - VBoxContainer *font_size_items_list; - VBoxContainer *icon_items_list; - VBoxContainer *stylebox_items_list; + TabContainer *data_type_tabs = nullptr; + VBoxContainer *color_items_list = nullptr; + VBoxContainer *constant_items_list = nullptr; + VBoxContainer *font_items_list = nullptr; + VBoxContainer *font_size_items_list = nullptr; + VBoxContainer *icon_items_list = nullptr; + VBoxContainer *stylebox_items_list = nullptr; - LineEdit *type_variation_edit; - Button *type_variation_button; - Label *type_variation_locked; + LineEdit *type_variation_edit = nullptr; + Button *type_variation_button = nullptr; + Label *type_variation_locked = nullptr; enum TypeDialogMode { ADD_THEME_TYPE, @@ -345,15 +358,15 @@ class ThemeTypeEditor : public MarginContainer { }; TypeDialogMode add_type_mode = ADD_THEME_TYPE; - ThemeTypeDialog *add_type_dialog; + ThemeTypeDialog *add_type_dialog = nullptr; Vector<Control *> focusables; - Timer *update_debounce_timer; + Timer *update_debounce_timer = nullptr; VBoxContainer *_create_item_list(Theme::DataType p_data_type); void _update_type_list(); void _update_type_list_debounced(); - OrderedHashMap<StringName, bool> _get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default); + HashMap<StringName, bool> _get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default); HBoxContainer *_create_property_control(Theme::DataType p_data_type, String p_item_name, bool p_editable); void _add_focusable(Control *p_control); void _update_type_items(); @@ -374,7 +387,7 @@ class ThemeTypeEditor : public MarginContainer { void _color_item_changed(Color p_value, String p_item_name); void _constant_item_changed(float p_value, String p_item_name); void _font_size_item_changed(float p_value, String p_item_name); - void _edit_resource_item(RES p_resource, bool p_edit); + void _edit_resource_item(Ref<Resource> p_resource, bool p_edit); void _font_item_changed(Ref<Font> p_value, String p_item_name); void _icon_item_changed(Ref<Texture2D> p_value, String p_item_name); void _stylebox_item_changed(Ref<StyleBox> p_value, String p_item_name); @@ -407,15 +420,15 @@ class ThemeEditor : public VBoxContainer { Ref<Theme> theme; - TabBar *preview_tabs; - PanelContainer *preview_tabs_content; - Button *add_preview_button; - EditorFileDialog *preview_scene_dialog; + TabBar *preview_tabs = nullptr; + PanelContainer *preview_tabs_content = nullptr; + Button *add_preview_button = nullptr; + EditorFileDialog *preview_scene_dialog = nullptr; - ThemeTypeEditor *theme_type_editor; + ThemeTypeEditor *theme_type_editor = nullptr; - Label *theme_name; - ThemeItemEditorDialog *theme_edit_dialog; + Label *theme_name = nullptr; + ThemeItemEditorDialog *theme_edit_dialog = nullptr; void _theme_save_button_cbk(bool p_save_as); void _theme_edit_button_cbk(); @@ -442,9 +455,8 @@ public: class ThemeEditorPlugin : public EditorPlugin { GDCLASS(ThemeEditorPlugin, EditorPlugin); - ThemeEditor *theme_editor; - EditorNode *editor; - Button *button; + ThemeEditor *theme_editor = nullptr; + Button *button = nullptr; public: virtual String get_name() const override { return "Theme"; } @@ -453,7 +465,7 @@ public: virtual bool handles(Object *p_node) const override; virtual void make_visible(bool p_visible) override; - ThemeEditorPlugin(EditorNode *p_node); + ThemeEditorPlugin(); }; #endif // THEME_EDITOR_PLUGIN_H diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp index c4ef6e086d..5218ef67c5 100644 --- a/editor/plugins/theme_editor_preview.cpp +++ b/editor/plugins/theme_editor_preview.cpp @@ -1,40 +1,49 @@ -/*************************************************************************/ -/* theme_editor_preview.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* theme_editor_preview.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 "theme_editor_preview.h" +#include "core/config/project_settings.h" #include "core/input/input.h" #include "core/math/math_funcs.h" -#include "scene/resources/packed_scene.h" - +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "scene/gui/button.h" +#include "scene/gui/check_box.h" +#include "scene/gui/check_button.h" +#include "scene/gui/color_picker.h" +#include "scene/gui/progress_bar.h" +#include "scene/gui/text_edit.h" +#include "scene/gui/tree.h" +#include "scene/resources/packed_scene.h" +#include "scene/theme/theme_db.h" constexpr double REFRESH_TIMER = 1.5; @@ -50,7 +59,7 @@ void ThemeEditorPreview::add_preview_overlay(Control *p_overlay) { void ThemeEditorPreview::_propagate_redraw(Control *p_at) { p_at->notification(NOTIFICATION_THEME_CHANGED); p_at->update_minimum_size(); - p_at->update(); + p_at->queue_redraw(); for (int i = 0; i < p_at->get_child_count(); i++) { Control *a = Object::cast_to<Control>(p_at->get_child(i)); if (a) { @@ -122,7 +131,7 @@ void ThemeEditorPreview::_draw_picker_overlay() { } Rect2 highlight_label_rect = highlight_rect; - highlight_label_rect.size = theme_cache.preview_picker_font->get_string_size(highlight_name, theme_cache.font_size); + highlight_label_rect.size = theme_cache.preview_picker_font->get_string_size(highlight_name, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size); int margin_top = theme_cache.preview_picker_label->get_margin(SIDE_TOP); int margin_left = theme_cache.preview_picker_label->get_margin(SIDE_LEFT); @@ -168,7 +177,7 @@ void ThemeEditorPreview::_gui_input_picker_overlay(const Ref<InputEvent> &p_even if (mm.is_valid()) { Vector2 mp = preview_content->get_local_mouse_position(); hovered_control = _find_hovered_control(preview_content, mp); - picker_overlay->update(); + picker_overlay->queue_redraw(); } // Forward input to the scroll container underneath to allow scrolling. @@ -177,7 +186,7 @@ void ThemeEditorPreview::_gui_input_picker_overlay(const Ref<InputEvent> &p_even void ThemeEditorPreview::_reset_picker_overlay() { hovered_control = nullptr; - picker_overlay->update(); + picker_overlay->queue_redraw(); } void ThemeEditorPreview::_notification(int p_what) { @@ -199,6 +208,7 @@ void ThemeEditorPreview::_notification(int p_what) { theme_cache.preview_picker_font = get_theme_font(SNAME("status_source"), SNAME("EditorFonts")); theme_cache.font_size = get_theme_font_size(SNAME("font_size"), SNAME("EditorFonts")); } break; + case NOTIFICATION_PROCESS: { time_left -= get_process_delta_time(); if (time_left < 0) { @@ -221,7 +231,7 @@ ThemeEditorPreview::ThemeEditorPreview() { preview_toolbar->add_child(picker_button); picker_button->set_flat(true); picker_button->set_toggle_mode(true); - picker_button->set_tooltip(TTR("Toggle the control picker, allowing to visually select control types for edit.")); + picker_button->set_tooltip_text(TTR("Toggle the control picker, allowing to visually select control types for edit.")); picker_button->connect("pressed", callable_mp(this, &ThemeEditorPreview::_picker_button_cbk)); MarginContainer *preview_body = memnew(MarginContainer); @@ -234,14 +244,14 @@ ThemeEditorPreview::ThemeEditorPreview() { MarginContainer *preview_root = memnew(MarginContainer); preview_container->add_child(preview_root); - preview_root->set_theme(Theme::get_default()); + preview_root->set_theme(ThemeDB::get_singleton()->get_default_theme()); preview_root->set_clip_contents(true); preview_root->set_custom_minimum_size(Size2(450, 0) * EDSCALE); preview_root->set_v_size_flags(SIZE_EXPAND_FILL); preview_root->set_h_size_flags(SIZE_EXPAND_FILL); preview_bg = memnew(ColorRect); - preview_bg->set_anchors_and_offsets_preset(PRESET_WIDE); + preview_bg->set_anchors_and_offsets_preset(PRESET_FULL_RECT); preview_bg->set_color(GLOBAL_GET("rendering/environment/defaults/default_clear_color")); preview_root->add_child(preview_bg); @@ -264,6 +274,15 @@ ThemeEditorPreview::ThemeEditorPreview() { picker_overlay->connect("mouse_exited", callable_mp(this, &ThemeEditorPreview::_reset_picker_overlay)); } +void DefaultThemeEditorPreview::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + test_color_picker_button->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("color_picker_button_height"), SNAME("Editor")))); + } break; + } +} + DefaultThemeEditorPreview::DefaultThemeEditorPreview() { Panel *main_panel = memnew(Panel); preview_content->add_child(main_panel); @@ -338,7 +357,8 @@ DefaultThemeEditorPreview::DefaultThemeEditorPreview() { test_option_button->add_item(TTR("Many")); test_option_button->add_item(TTR("Options")); first_vb->add_child(test_option_button); - first_vb->add_child(memnew(ColorPickerButton)); + test_color_picker_button = memnew(ColorPickerButton); + first_vb->add_child(test_color_picker_button); VBoxContainer *second_vb = memnew(VBoxContainer); second_vb->set_h_size_flags(SIZE_EXPAND_FILL); @@ -442,7 +462,7 @@ void SceneThemeEditorPreview::_reload_scene() { for (int i = preview_content->get_child_count() - 1; i >= 0; i--) { Node *node = preview_content->get_child(i); - node->queue_delete(); + node->queue_free(); preview_content->remove_child(node); } @@ -501,7 +521,7 @@ SceneThemeEditorPreview::SceneThemeEditorPreview() { reload_scene_button = memnew(Button); reload_scene_button->set_flat(true); - reload_scene_button->set_tooltip(TTR("Reload the scene to reflect its most actual state.")); + reload_scene_button->set_tooltip_text(TTR("Reload the scene to reflect its most actual state.")); preview_toolbar->add_child(reload_scene_button); reload_scene_button->connect("pressed", callable_mp(this, &SceneThemeEditorPreview::_reload_scene)); } diff --git a/editor/plugins/theme_editor_preview.h b/editor/plugins/theme_editor_preview.h index a509ae3c50..640a931c99 100644 --- a/editor/plugins/theme_editor_preview.h +++ b/editor/plugins/theme_editor_preview.h @@ -1,64 +1,52 @@ -/*************************************************************************/ -/* theme_editor_preview.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* theme_editor_preview.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 THEME_EDITOR_PREVIEW_H #define THEME_EDITOR_PREVIEW_H #include "scene/gui/box_container.h" -#include "scene/gui/check_box.h" -#include "scene/gui/check_button.h" -#include "scene/gui/color_picker.h" +#include "scene/gui/button.h" #include "scene/gui/color_rect.h" -#include "scene/gui/label.h" #include "scene/gui/margin_container.h" -#include "scene/gui/menu_button.h" -#include "scene/gui/option_button.h" -#include "scene/gui/panel.h" -#include "scene/gui/progress_bar.h" #include "scene/gui/scroll_container.h" -#include "scene/gui/separator.h" -#include "scene/gui/spin_box.h" -#include "scene/gui/tab_container.h" -#include "scene/gui/text_edit.h" -#include "scene/gui/tree.h" #include "scene/resources/theme.h" -#include "editor/editor_node.h" +class ColorPickerButton; class ThemeEditorPreview : public VBoxContainer { GDCLASS(ThemeEditorPreview, VBoxContainer); - ScrollContainer *preview_container; - ColorRect *preview_bg; - MarginContainer *preview_overlay; - Control *picker_overlay; + ScrollContainer *preview_container = nullptr; + ColorRect *preview_bg = nullptr; + MarginContainer *preview_overlay = nullptr; + Control *picker_overlay = nullptr; Control *hovered_control = nullptr; struct ThemeCache { @@ -83,9 +71,9 @@ class ThemeEditorPreview : public VBoxContainer { void _reset_picker_overlay(); protected: - HBoxContainer *preview_toolbar; - MarginContainer *preview_content; - Button *picker_button; + HBoxContainer *preview_toolbar = nullptr; + MarginContainer *preview_content = nullptr; + Button *picker_button = nullptr; void add_preview_overlay(Control *p_overlay); @@ -101,6 +89,11 @@ public: class DefaultThemeEditorPreview : public ThemeEditorPreview { GDCLASS(DefaultThemeEditorPreview, ThemeEditorPreview); + ColorPickerButton *test_color_picker_button = nullptr; + +protected: + void _notification(int p_what); + public: DefaultThemeEditorPreview(); }; @@ -110,7 +103,7 @@ class SceneThemeEditorPreview : public ThemeEditorPreview { Ref<PackedScene> loaded_scene; - Button *reload_scene_button; + Button *reload_scene_button = nullptr; void _reload_scene(); diff --git a/editor/plugins/tiles/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp index bf30b595fe..eaf72d36ba 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.cpp +++ b/editor/plugins/tiles/atlas_merging_dialog.cpp @@ -1,36 +1,38 @@ -/*************************************************************************/ -/* atlas_merging_dialog.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* atlas_merging_dialog.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 "atlas_merging_dialog.h" +#include "editor/editor_file_dialog.h" #include "editor/editor_scale.h" +#include "editor/editor_undo_redo_manager.h" #include "scene/gui/control.h" #include "scene/gui/split_container.h" @@ -44,9 +46,7 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla merged_mapping.clear(); if (p_atlas_sources.size() >= 2) { - Ref<Image> output_image; - output_image.instantiate(); - output_image->create(1, 1, false, Image::FORMAT_RGBA8); + Ref<Image> output_image = Image::create_empty(1, 1, false, Image::FORMAT_RGBA8); // Compute the new texture region size. Vector2i new_texture_region_size; @@ -55,12 +55,12 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla new_texture_region_size = new_texture_region_size.max(atlas_source->get_texture_region_size()); } - // Generate the merged TileSetAtlasSource. + // Generate the new texture. Vector2i atlas_offset; int line_height = 0; for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) { Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index]; - merged_mapping.push_back(Map<Vector2i, Vector2i>()); + merged_mapping.push_back(HashMap<Vector2i, Vector2i>()); // Layout the tiles. Vector2i atlas_size; @@ -71,28 +71,6 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla Rect2i new_tile_rect_in_altas = Rect2i(atlas_offset + tile_id, atlas_source->get_tile_size_in_atlas(tile_id)); - // Create tiles and alternatives, then copy their properties. - for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_id); alternative_index++) { - int alternative_id = atlas_source->get_alternative_tile_id(tile_id, alternative_index); - if (alternative_id == 0) { - merged->create_tile(new_tile_rect_in_altas.position, new_tile_rect_in_altas.size); - } else { - merged->create_alternative_tile(new_tile_rect_in_altas.position, alternative_index); - } - - // Copy the properties. - TileData *original_tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id)); - List<PropertyInfo> properties; - original_tile_data->get_property_list(&properties); - for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { - const StringName &property_name = E->get().name; - merged->set(property_name, original_tile_data->get(property_name)); - } - - // Add to the mapping. - merged_mapping[source_index][tile_id] = new_tile_rect_in_altas.position; - } - // Copy the texture. for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(tile_id); frame++) { Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id, frame); @@ -102,6 +80,9 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla } output_image->blit_rect(atlas_source->get_texture()->get_image(), src_rect, dst_rect_wide.get_center() - src_rect.size / 2); } + + // Add to the mapping. + merged_mapping[source_index][tile_id] = new_tile_rect_in_altas.position; } // Compute the atlas offset. @@ -114,12 +95,43 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla } } - Ref<ImageTexture> output_image_texture; - output_image_texture.instantiate(); - output_image_texture->create_from_image(output_image); + merged->set_texture(ImageTexture::create_from_image(output_image)); + + // Copy the tiles to the merged TileSetAtlasSource. + for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) { + Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index]; + for (KeyValue<Vector2i, Vector2i> tile_mapping : merged_mapping[source_index]) { + // Create tiles and alternatives, then copy their properties. + for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_mapping.key); alternative_index++) { + int alternative_id = atlas_source->get_alternative_tile_id(tile_mapping.key, alternative_index); + if (alternative_id == 0) { + merged->create_tile(tile_mapping.value, atlas_source->get_tile_size_in_atlas(tile_mapping.key)); + } else { + merged->create_alternative_tile(tile_mapping.value, alternative_index); + } + + // Copy the properties. + TileData *src_tile_data = atlas_source->get_tile_data(tile_mapping.key, alternative_id); + List<PropertyInfo> properties; + src_tile_data->get_property_list(&properties); + + TileData *dst_tile_data = merged->get_tile_data(tile_mapping.value, alternative_id); + for (PropertyInfo property : properties) { + if (!(property.usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + Variant value = src_tile_data->get(property.name); + Variant default_value = ClassDB::class_get_default_property_value("TileData", property.name); + if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) { + continue; + } + dst_tile_data->set(property.name, value); + } + } + } + } merged->set_name(p_atlas_sources[0]->get_name()); - merged->set_texture(output_image_texture); merged->set_texture_region_size(new_texture_region_size); } } @@ -154,9 +166,12 @@ void AtlasMergingDialog::_merge_confirmed(String p_path) { Ref<ImageTexture> output_image_texture = merged->get_texture(); output_image_texture->get_image()->save_png(p_path); + ResourceLoader::import(p_path); + Ref<Texture2D> new_texture_resource = ResourceLoader::load(p_path, "Texture2D"); merged->set_texture(new_texture_resource); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Merge TileSetAtlasSource")); int next_id = tile_set->get_next_source_id(); undo_redo->add_do_method(*tile_set, "add_source", merged, next_id); @@ -196,6 +211,7 @@ void AtlasMergingDialog::ok_pressed() { } void AtlasMergingDialog::cancel_pressed() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); for (int i = 0; i < commited_actions_count; i++) { undo_redo->undo(); } @@ -226,6 +242,14 @@ bool AtlasMergingDialog::_get(const StringName &p_name, Variant &r_ret) const { return false; } +void AtlasMergingDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: { + _update_texture(); + } break; + } +} + void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) { ERR_FAIL_COND(!p_tile_set.is_valid()); tile_set = p_tile_set; @@ -237,9 +261,9 @@ void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) { if (atlas_source.is_valid()) { Ref<Texture2D> texture = atlas_source->get_texture(); if (texture.is_valid()) { - String item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id); + String item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id); atlas_merging_atlases_list->add_item(item_text, texture); - atlas_merging_atlases_list->set_item_metadata(atlas_merging_atlases_list->get_item_count() - 1, source_id); + atlas_merging_atlases_list->set_item_metadata(-1, source_id); } } } @@ -256,7 +280,7 @@ AtlasMergingDialog::AtlasMergingDialog() { set_hide_on_ok(false); // Ok buttons - get_ok_button()->set_text(TTR("Merge (Keep original Atlases)")); + set_ok_button_text(TTR("Merge (Keep original Atlases)")); get_ok_button()->set_disabled(true); merge_button = add_button(TTR("Merge"), true, "merge"); merge_button->set_disabled(true); @@ -268,7 +292,7 @@ AtlasMergingDialog::AtlasMergingDialog() { // Atlas sources item list. atlas_merging_atlases_list = memnew(ItemList); - atlas_merging_atlases_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE); + atlas_merging_atlases_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE); atlas_merging_atlases_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); atlas_merging_atlases_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); atlas_merging_atlases_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); @@ -301,7 +325,7 @@ AtlasMergingDialog::AtlasMergingDialog() { preview = memnew(TextureRect); preview->set_h_size_flags(Control::SIZE_EXPAND_FILL); preview->set_v_size_flags(Control::SIZE_EXPAND_FILL); - preview->set_expand(true); + preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); preview->hide(); preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); atlas_merging_right_panel->add_child(preview); diff --git a/editor/plugins/tiles/atlas_merging_dialog.h b/editor/plugins/tiles/atlas_merging_dialog.h index 2ae94cf44a..bf1b56894f 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.h +++ b/editor/plugins/tiles/atlas_merging_dialog.h @@ -1,44 +1,44 @@ -/*************************************************************************/ -/* atlas_merging_dialog.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* atlas_merging_dialog.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 ATLAS_MERGING_DIALOG_H #define ATLAS_MERGING_DIALOG_H -#include "editor/editor_node.h" #include "editor/editor_properties.h" - #include "scene/gui/dialogs.h" #include "scene/gui/item_list.h" #include "scene/gui/texture_rect.h" #include "scene/resources/tile_set.h" +class EditorFileDialog; + class AtlasMergingDialog : public ConfirmationDialog { GDCLASS(AtlasMergingDialog, ConfirmationDialog); @@ -46,22 +46,20 @@ private: int commited_actions_count = 0; bool delete_original_atlases = true; Ref<TileSetAtlasSource> merged; - LocalVector<Map<Vector2i, Vector2i>> merged_mapping; + LocalVector<HashMap<Vector2i, Vector2i>> merged_mapping; Ref<TileSet> tile_set; - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); - // Settings. int next_line_after_column = 30; // GUI. - ItemList *atlas_merging_atlases_list; - EditorPropertyVector2i *texture_region_size_editor_property; - EditorPropertyInteger *columns_editor_property; - TextureRect *preview; - Label *select_2_atlases_label; - EditorFileDialog *editor_file_dialog; - Button *merge_button; + ItemList *atlas_merging_atlases_list = nullptr; + EditorPropertyVector2i *texture_region_size_editor_property = nullptr; + EditorPropertyInteger *columns_editor_property = nullptr; + TextureRect *preview = nullptr; + Label *select_2_atlases_label = nullptr; + EditorFileDialog *editor_file_dialog = nullptr; + Button *merge_button = nullptr; void _property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing); @@ -77,6 +75,8 @@ protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; + void _notification(int p_what); + public: void update_tile_set(Ref<TileSet> p_tile_set); diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index c85956991a..43c6d1a48b 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* tile_atlas_view.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_atlas_view.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 "tile_atlas_view.h" @@ -36,58 +36,27 @@ #include "scene/gui/box_container.h" #include "scene/gui/label.h" #include "scene/gui/panel.h" -#include "scene/gui/texture_rect.h" +#include "scene/gui/view_panner.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" void TileAtlasView::gui_input(const Ref<InputEvent> &p_event) { - Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid()) { - drag_type = DRAG_TYPE_NONE; - - Vector2i scroll_vec = Vector2((mb->get_button_index() == MouseButton::WHEEL_LEFT) - (mb->get_button_index() == MouseButton::WHEEL_RIGHT), (mb->get_button_index() == MouseButton::WHEEL_UP) - (mb->get_button_index() == MouseButton::WHEEL_DOWN)); - if (scroll_vec != Vector2()) { - if (mb->is_ctrl_pressed()) { - if (mb->is_shift_pressed()) { - panning.x += 32 * mb->get_factor() * scroll_vec.y; - panning.y += 32 * mb->get_factor() * scroll_vec.x; - } else { - panning.y += 32 * mb->get_factor() * scroll_vec.y; - panning.x += 32 * mb->get_factor() * scroll_vec.x; - } - - emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning); - _update_zoom_and_panning(true); - accept_event(); - - } else if (!mb->is_shift_pressed()) { - zoom_widget->set_zoom_by_increments(scroll_vec.y * 2); - emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning); - _update_zoom_and_panning(true); - accept_event(); - } - } - - if (mb->get_button_index() == MouseButton::MIDDLE || mb->get_button_index() == MouseButton::RIGHT) { - if (mb->is_pressed()) { - drag_type = DRAG_TYPE_PAN; - } else { - drag_type = DRAG_TYPE_NONE; - } - accept_event(); - } + if (panner->gui_input(p_event)) { + accept_event(); } +} - Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid()) { - if (drag_type == DRAG_TYPE_PAN) { - panning += mm->get_relative(); - _update_zoom_and_panning(); - emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning); - accept_event(); - } - } +void TileAtlasView::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) { + panning += p_scroll_vec; + _update_zoom_and_panning(true); + emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning); +} + +void TileAtlasView::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) { + zoom_widget->set_zoom(zoom_widget->get_zoom() * p_zoom_factor); + _update_zoom_and_panning(true); + emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning); } Size2i TileAtlasView::_compute_base_tiles_control_size() { @@ -109,7 +78,7 @@ Size2i TileAtlasView::_compute_alternative_tiles_control_size() { Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(tile_id).size; for (int j = 1; j < alternatives_count; j++) { int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j); - bool transposed = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(tile_id, alternative_id))->get_transpose(); + bool transposed = tile_set_atlas_source->get_tile_data(tile_id, alternative_id)->get_transpose(); line_size.x += transposed ? texture_region_size.y : texture_region_size.x; line_size.y = MAX(line_size.y, transposed ? texture_region_size.x : texture_region_size.y); } @@ -121,6 +90,8 @@ Size2i TileAtlasView::_compute_alternative_tiles_control_size() { } void TileAtlasView::_update_zoom_and_panning(bool p_zoom_on_mouse_pos) { + // Don't allow zoom to go below 1% or above 10000% + zoom_widget->set_zoom(CLAMP(zoom_widget->get_zoom(), 0.01f, 100.f)); float zoom = zoom_widget->get_zoom(); // Compute the minimum sizes. @@ -152,8 +123,8 @@ void TileAtlasView::_update_zoom_and_panning(bool p_zoom_on_mouse_pos) { } // Update the backgrounds. - background_left->update(); - background_right->update(); + background_left->set_size(base_tiles_root_control->get_custom_minimum_size()); + background_right->set_size(alternative_tiles_root_control->get_custom_minimum_size()); // Zoom on the position. if (p_zoom_on_mouse_pos) { @@ -164,7 +135,7 @@ void TileAtlasView::_update_zoom_and_panning(bool p_zoom_on_mouse_pos) { // Center of panel. panning = panning * zoom / previous_zoom; } - button_center_view->set_disabled(panning.is_equal_approx(Vector2())); + button_center_view->set_disabled(panning.is_zero_approx()); previous_zoom = zoom; @@ -185,7 +156,7 @@ void TileAtlasView::_center_view() { } void TileAtlasView::_base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) { - base_tiles_root_control->set_tooltip(""); + base_tiles_root_control->set_tooltip_text(""); Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { @@ -194,7 +165,7 @@ void TileAtlasView::_base_tiles_root_control_gui_input(const Ref<InputEvent> &p_ if (coords != TileSetSource::INVALID_ATLAS_COORDS) { coords = tile_set_atlas_source->get_tile_at_coords(coords); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - base_tiles_root_control->set_tooltip(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: 0"), source_id, coords)); + base_tiles_root_control->set_tooltip_text(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: 0"), source_id, coords)); } } } @@ -217,6 +188,19 @@ void TileAtlasView::_draw_base_tiles() { rect = rect.intersection(Rect2i(Vector2(), texture->get_size())); if (rect.size.x > 0 && rect.size.y > 0) { base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + } + } + } + } + + // Draw dark overlay after for performance reasons. + for (int x = 0; x < grid_size.x; x++) { + for (int y = 0; y < grid_size.y; y++) { + Vector2i coords = Vector2i(x, y); + if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { + Rect2i rect = Rect2i((texture_region_size + separation) * coords + margins, texture_region_size + separation); + rect = rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (rect.size.x > 0 && rect.size.y > 0) { base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); } } @@ -263,27 +247,38 @@ void TileAtlasView::_draw_base_tiles() { for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) { // Update the y to max value. Rect2i base_frame_rect = tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame); - Vector2i offset_pos = base_frame_rect.get_center() + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0); + Vector2i offset_pos = base_frame_rect.get_center() + tile_set_atlas_source->get_tile_data(atlas_coords, 0)->get_texture_origin(); // Draw the tile. TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0, frame); + } + } - // Draw, the texture in the separation areas - if (separation.x > 0) { - Rect2i right_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(base_frame_rect.size.x, 0), Vector2i(separation.x, base_frame_rect.size.y)); - right_sep_rect = right_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); - if (right_sep_rect.size.x > 0 && right_sep_rect.size.y > 0) { - base_tiles_draw->draw_texture_rect_region(texture, right_sep_rect, right_sep_rect); - base_tiles_draw->draw_rect(right_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + // Draw Dark overlay on separation in its own pass. + if (separation.x > 0 || separation.y > 0) { + for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { + Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i); + + for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) { + // Update the y to max value. + Rect2i base_frame_rect = tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame); + + if (separation.x > 0) { + Rect2i right_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(base_frame_rect.size.x, 0), Vector2i(separation.x, base_frame_rect.size.y)); + right_sep_rect = right_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (right_sep_rect.size.x > 0 && right_sep_rect.size.y > 0) { + //base_tiles_draw->draw_texture_rect_region(texture, right_sep_rect, right_sep_rect); + base_tiles_draw->draw_rect(right_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + } } - } - if (separation.y > 0) { - Rect2i bottom_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(0, base_frame_rect.size.y), Vector2i(base_frame_rect.size.x + separation.x, separation.y)); - bottom_sep_rect = bottom_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); - if (bottom_sep_rect.size.x > 0 && bottom_sep_rect.size.y > 0) { - base_tiles_draw->draw_texture_rect_region(texture, bottom_sep_rect, bottom_sep_rect); - base_tiles_draw->draw_rect(bottom_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + if (separation.y > 0) { + Rect2i bottom_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(0, base_frame_rect.size.y), Vector2i(base_frame_rect.size.x + separation.x, separation.y)); + bottom_sep_rect = bottom_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (bottom_sep_rect.size.x > 0 && bottom_sep_rect.size.y > 0) { + //base_tiles_draw->draw_texture_rect_region(texture, bottom_sep_rect, bottom_sep_rect); + base_tiles_draw->draw_rect(bottom_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + } } } } @@ -323,28 +318,29 @@ void TileAtlasView::_draw_base_tiles_texture_grid() { void TileAtlasView::_draw_base_tiles_shape_grid() { // Draw the shapes. - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Vector2i tile_shape_size = tile_set->get_tile_size(); for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { Vector2i tile_id = tile_set_atlas_source->get_tile_id(i); - Vector2 in_tile_base_offset = tile_set_atlas_source->get_tile_effective_texture_offset(tile_id, 0); - - for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(tile_id); frame++) { - Color color = grid_color; - if (frame > 0) { - color.a *= 0.3; + Vector2 in_tile_base_offset = tile_set_atlas_source->get_tile_data(tile_id, 0)->get_texture_origin(); + if (tile_set_atlas_source->is_position_in_tile_texture_region(tile_id, 0, -tile_shape_size / 2) && tile_set_atlas_source->is_position_in_tile_texture_region(tile_id, 0, tile_shape_size / 2 - Vector2(1, 1))) { + for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(tile_id); frame++) { + Color color = grid_color; + if (frame > 0) { + color.a *= 0.3; + } + Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(tile_id, frame); + Transform2D tile_xform; + tile_xform.set_origin(texture_region.get_center() + in_tile_base_offset); + tile_xform.set_scale(tile_shape_size); + tile_set->draw_tile_shape(base_tiles_shape_grid, tile_xform, color); } - Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(tile_id); - Transform2D tile_xform; - tile_xform.set_origin(texture_region.get_center() + in_tile_base_offset); - tile_xform.set_scale(tile_shape_size); - tile_set->draw_tile_shape(base_tiles_shape_grid, tile_xform, color); } } } void TileAtlasView::_alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) { - alternative_tiles_root_control->set_tooltip(""); + alternative_tiles_root_control->set_tooltip_text(""); Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { @@ -353,7 +349,7 @@ void TileAtlasView::_alternative_tiles_root_control_gui_input(const Ref<InputEve Vector2i coords = Vector2i(coords3.x, coords3.y); int alternative_id = coords3.z; if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) { - alternative_tiles_root_control->set_tooltip(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: %d"), source_id, coords, alternative_id)); + alternative_tiles_root_control->set_tooltip_text(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: %d"), source_id, coords, alternative_id)); } } } @@ -371,16 +367,16 @@ void TileAtlasView::_draw_alternatives() { int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(atlas_coords); for (int j = 1; j < alternatives_count; j++) { int alternative_id = tile_set_atlas_source->get_alternative_tile_id(atlas_coords, j); - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(atlas_coords, alternative_id)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(atlas_coords, alternative_id); bool transposed = tile_data->get_transpose(); // Update the y to max value. - Vector2i offset_pos = current_pos; + Vector2i offset_pos; if (transposed) { - offset_pos = (current_pos + Vector2(texture_region_size.y, texture_region_size.x) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id)); + offset_pos = (current_pos + Vector2(texture_region_size.y, texture_region_size.x) / 2 + tile_data->get_texture_origin()); y_increment = MAX(y_increment, texture_region_size.x); } else { - offset_pos = (current_pos + texture_region_size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id)); + offset_pos = (current_pos + texture_region_size / 2 + tile_data->get_texture_origin()); y_increment = MAX(y_increment, texture_region_size.y); } @@ -399,24 +395,25 @@ void TileAtlasView::_draw_alternatives() { void TileAtlasView::_draw_background_left() { Ref<Texture2D> texture = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); - background_left->set_size(base_tiles_root_control->get_custom_minimum_size()); background_left->draw_texture_rect(texture, Rect2(Vector2(), background_left->get_size()), true); } void TileAtlasView::_draw_background_right() { Ref<Texture2D> texture = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); - background_right->set_size(alternative_tiles_root_control->get_custom_minimum_size()); background_right->draw_texture_rect(texture, Rect2(Vector2(), background_right->get_size()), true); } void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) { - ERR_FAIL_COND(!p_tile_set); - ERR_FAIL_COND(!p_tile_set_atlas_source); + tile_set = p_tile_set; + tile_set_atlas_source = p_tile_set_atlas_source; + + if (!tile_set) { + return; + } + ERR_FAIL_COND(p_source_id < 0); ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source); - tile_set = p_tile_set; - tile_set_atlas_source = p_tile_set_atlas_source; source_id = p_source_id; // Show or hide the view. @@ -430,30 +427,16 @@ void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_ // Update everything. _update_zoom_and_panning(); - // Change children control size. - Size2i base_tiles_control_size = _compute_base_tiles_control_size(); - for (int i = 0; i < base_tiles_drawing_root->get_child_count(); i++) { - Control *control = Object::cast_to<Control>(base_tiles_drawing_root->get_child(i)); - if (control) { - control->set_size(base_tiles_control_size); - } - } - - Size2i alternative_control_size = _compute_alternative_tiles_control_size(); - for (int i = 0; i < alternative_tiles_drawing_root->get_child_count(); i++) { - Control *control = Object::cast_to<Control>(alternative_tiles_drawing_root->get_child(i)); - if (control) { - control->set_size(alternative_control_size); - } - } + base_tiles_drawing_root->set_size(_compute_base_tiles_control_size()); + alternative_tiles_drawing_root->set_size(_compute_alternative_tiles_control_size()); // Update. - base_tiles_draw->update(); - base_tiles_texture_grid->update(); - base_tiles_shape_grid->update(); - alternatives_draw->update(); - background_left->update(); - background_right->update(); + base_tiles_draw->queue_redraw(); + base_tiles_texture_grid->queue_redraw(); + base_tiles_shape_grid->queue_redraw(); + alternatives_draw->queue_redraw(); + background_left->queue_redraw(); + background_right->queue_redraw(); } float TileAtlasView::get_zoom() const { @@ -471,21 +454,31 @@ void TileAtlasView::set_padding(Side p_side, int p_padding) { margin_container_paddings[p_side] = p_padding; } -Vector2i TileAtlasView::get_atlas_tile_coords_at_pos(const Vector2 p_pos) const { +Vector2i TileAtlasView::get_atlas_tile_coords_at_pos(const Vector2 p_pos, bool p_clamp) const { Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); - if (texture.is_valid()) { - Vector2i margins = tile_set_atlas_source->get_margins(); - Vector2i separation = tile_set_atlas_source->get_separation(); - Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size(); + if (!texture.is_valid()) { + return TileSetSource::INVALID_ATLAS_COORDS; + } - // Compute index in atlas - Vector2 pos = p_pos - margins; - Vector2i ret = (pos / (texture_region_size + separation)).floor(); + Vector2i margins = tile_set_atlas_source->get_margins(); + Vector2i separation = tile_set_atlas_source->get_separation(); + Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size(); - return ret; + // Compute index in atlas + Vector2 pos = p_pos - margins; + Vector2i ret = (pos / (texture_region_size + separation)).floor(); + + // Return invalid value (without clamp). + Rect2i rect = Rect2(Vector2i(), tile_set_atlas_source->get_atlas_grid_size()); + if (!p_clamp && !rect.has_point(ret)) { + return TileSetSource::INVALID_ATLAS_COORDS; } - return TileSetSource::INVALID_ATLAS_COORDS; + // Clamp. + ret.x = CLAMP(ret.x, 0, rect.size.x - 1); + ret.y = CLAMP(ret.y, 0, rect.size.y - 1); + + return ret; } void TileAtlasView::_update_alternative_tiles_rect_cache() { @@ -499,13 +492,13 @@ void TileAtlasView::_update_alternative_tiles_rect_cache() { int line_height = 0; for (int j = 1; j < alternatives_count; j++) { int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j); - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(tile_id, alternative_id)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(tile_id, alternative_id); bool transposed = tile_data->get_transpose(); current.size = transposed ? Vector2i(texture_region_size.y, texture_region_size.x) : texture_region_size; // Update the rect. if (!alternative_tiles_rect_cache.has(tile_id)) { - alternative_tiles_rect_cache[tile_id] = Map<int, Rect2i>(); + alternative_tiles_rect_cache[tile_id] = HashMap<int, Rect2i>(); } alternative_tiles_rect_cache[tile_id][alternative_id] = current; @@ -519,7 +512,7 @@ void TileAtlasView::_update_alternative_tiles_rect_cache() { } Vector3i TileAtlasView::get_alternative_tile_at_pos(const Vector2 p_pos) const { - for (const KeyValue<Vector2, Map<int, Rect2i>> &E_coords : alternative_tiles_rect_cache) { + for (const KeyValue<Vector2, HashMap<int, Rect2i>> &E_coords : alternative_tiles_rect_cache) { for (const KeyValue<int, Rect2i> &E_alternative : E_coords.value) { if (E_alternative.value.has_point(p_pos)) { return Vector3i(E_coords.key.x, E_coords.key.y, E_alternative.key); @@ -537,20 +530,25 @@ Rect2i TileAtlasView::get_alternative_tile_rect(const Vector2i p_coords, int p_a return alternative_tiles_rect_cache[p_coords][p_alternative_tile]; } -void TileAtlasView::update() { - base_tiles_draw->update(); - base_tiles_texture_grid->update(); - base_tiles_shape_grid->update(); - alternatives_draw->update(); - background_left->update(); - background_right->update(); +void TileAtlasView::queue_redraw() { + base_tiles_draw->queue_redraw(); + base_tiles_texture_grid->queue_redraw(); + base_tiles_shape_grid->queue_redraw(); + alternatives_draw->queue_redraw(); + background_left->queue_redraw(); + background_right->queue_redraw(); } void TileAtlasView::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: + case NOTIFICATION_ENTER_TREE: + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); + } break; + + case NOTIFICATION_READY: { button_center_view->set_icon(get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons"))); - break; + } break; } } @@ -564,16 +562,16 @@ TileAtlasView::TileAtlasView() { Panel *panel = memnew(Panel); panel->set_clip_contents(true); panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - panel->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); panel->set_h_size_flags(SIZE_EXPAND_FILL); panel->set_v_size_flags(SIZE_EXPAND_FILL); add_child(panel); - // Scrollingsc zoom_widget = memnew(EditorZoomWidget); add_child(zoom_widget); zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE); zoom_widget->connect("zoom_changed", callable_mp(this, &TileAtlasView::_zoom_widget_changed).unbind(1)); + zoom_widget->set_shortcut_context(this); button_center_view = memnew(Button); button_center_view->set_icon(get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons"))); @@ -581,13 +579,19 @@ TileAtlasView::TileAtlasView() { button_center_view->connect("pressed", callable_mp(this, &TileAtlasView::_center_view)); button_center_view->set_flat(true); button_center_view->set_disabled(true); - button_center_view->set_tooltip(TTR("Center View")); + button_center_view->set_tooltip_text(TTR("Center View")); add_child(button_center_view); + panner.instantiate(); + panner->set_callbacks(callable_mp(this, &TileAtlasView::_pan_callback), callable_mp(this, &TileAtlasView::_zoom_callback)); + panner->set_enable_rmb(true); + center_container = memnew(CenterContainer); center_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); center_container->set_anchors_preset(Control::PRESET_CENTER); center_container->connect("gui_input", callable_mp(this, &TileAtlasView::gui_input)); + center_container->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key)); + center_container->set_focus_mode(FOCUS_CLICK); panel->add_child(center_container); missing_source_label = memnew(Label); @@ -627,32 +631,31 @@ TileAtlasView::TileAtlasView() { background_left = memnew(Control); background_left->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - background_left->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + background_left->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT); background_left->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED); background_left->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_left)); base_tiles_root_control->add_child(background_left); base_tiles_drawing_root = memnew(Control); base_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - base_tiles_drawing_root->set_anchors_and_offsets_preset(Control::PRESET_WIDE); base_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST); base_tiles_root_control->add_child(base_tiles_drawing_root); base_tiles_draw = memnew(Control); base_tiles_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - base_tiles_draw->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + base_tiles_draw->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); base_tiles_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles)); base_tiles_drawing_root->add_child(base_tiles_draw); base_tiles_texture_grid = memnew(Control); base_tiles_texture_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - base_tiles_texture_grid->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + base_tiles_texture_grid->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); base_tiles_texture_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_texture_grid)); base_tiles_drawing_root->add_child(base_tiles_texture_grid); base_tiles_shape_grid = memnew(Control); base_tiles_shape_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - base_tiles_shape_grid->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + base_tiles_shape_grid->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); base_tiles_shape_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_shape_grid)); base_tiles_drawing_root->add_child(base_tiles_shape_grid); @@ -665,14 +668,15 @@ TileAtlasView::TileAtlasView() { alternative_tiles_root_control = memnew(Control); alternative_tiles_root_control->set_mouse_filter(Control::MOUSE_FILTER_PASS); + alternative_tiles_root_control->set_v_size_flags(Control::SIZE_EXPAND_FILL); alternative_tiles_root_control->connect("gui_input", callable_mp(this, &TileAtlasView::_alternative_tiles_root_control_gui_input)); right_vbox->add_child(alternative_tiles_root_control); background_right = memnew(Control); background_right->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + background_right->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT); background_right->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED); background_right->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_right)); - alternative_tiles_root_control->add_child(background_right); alternative_tiles_drawing_root = memnew(Control); @@ -682,6 +686,7 @@ TileAtlasView::TileAtlasView() { alternatives_draw = memnew(Control); alternatives_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + alternatives_draw->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); alternatives_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_alternatives)); alternative_tiles_drawing_root->add_child(alternatives_draw); } diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h index ca7f083132..4a7547f34b 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* tile_atlas_view.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_atlas_view.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 TILE_ATLAS_VIEW_H #define TILE_ATLAS_VIEW_H @@ -37,16 +37,16 @@ #include "scene/gui/center_container.h" #include "scene/gui/label.h" #include "scene/gui/margin_container.h" -#include "scene/gui/scroll_container.h" -#include "scene/gui/texture_rect.h" #include "scene/resources/tile_set.h" +class ViewPanner; + class TileAtlasView : public Control { GDCLASS(TileAtlasView, Control); private: - TileSet *tile_set; - TileSetAtlasSource *tile_set_atlas_source; + TileSet *tile_set = nullptr; + TileSetAtlasSource *tile_set_atlas_source = nullptr; int source_id = TileSet::INVALID_SOURCE; enum DragType { @@ -55,53 +55,57 @@ private: }; DragType drag_type = DRAG_TYPE_NONE; float previous_zoom = 1.0; - EditorZoomWidget *zoom_widget; - Button *button_center_view; - CenterContainer *center_container; + EditorZoomWidget *zoom_widget = nullptr; + Button *button_center_view = nullptr; + CenterContainer *center_container = nullptr; Vector2 panning; void _update_zoom_and_panning(bool p_zoom_on_mouse_pos = false); void _zoom_widget_changed(); void _center_view(); virtual void gui_input(const Ref<InputEvent> &p_event) override; - Map<Vector2, Map<int, Rect2i>> alternative_tiles_rect_cache; + Ref<ViewPanner> panner; + void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event); + void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event); + + HashMap<Vector2, HashMap<int, Rect2i>> alternative_tiles_rect_cache; void _update_alternative_tiles_rect_cache(); - MarginContainer *margin_container; + MarginContainer *margin_container = nullptr; int margin_container_paddings[4] = { 0, 0, 0, 0 }; - HBoxContainer *hbox; - Label *missing_source_label; + HBoxContainer *hbox = nullptr; + Label *missing_source_label = nullptr; // Background - Control *background_left; + Control *background_left = nullptr; void _draw_background_left(); - Control *background_right; + Control *background_right = nullptr; void _draw_background_right(); // Left side. - Control *base_tiles_root_control; + Control *base_tiles_root_control = nullptr; void _base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event); - Control *base_tiles_drawing_root; + Control *base_tiles_drawing_root = nullptr; - Control *base_tiles_draw; + Control *base_tiles_draw = nullptr; void _draw_base_tiles(); - Control *base_tiles_texture_grid; + Control *base_tiles_texture_grid = nullptr; void _draw_base_tiles_texture_grid(); - Control *base_tiles_shape_grid; + Control *base_tiles_shape_grid = nullptr; void _draw_base_tiles_shape_grid(); Size2i _compute_base_tiles_control_size(); // Right side. - Control *alternative_tiles_root_control; + Control *alternative_tiles_root_control = nullptr; void _alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event); - Control *alternative_tiles_drawing_root; + Control *alternative_tiles_drawing_root = nullptr; - Control *alternatives_draw; + Control *alternatives_draw = nullptr; void _draw_alternatives(); Size2i _compute_alternative_tiles_control_size(); @@ -123,7 +127,7 @@ public: void set_texture_grid_visible(bool p_visible) { base_tiles_texture_grid->set_visible(p_visible); }; void set_tile_shape_grid_visible(bool p_visible) { base_tiles_shape_grid->set_visible(p_visible); }; - Vector2i get_atlas_tile_coords_at_pos(const Vector2 p_pos) const; + Vector2i get_atlas_tile_coords_at_pos(const Vector2 p_pos, bool p_clamp = false) const; void add_control_over_atlas_tiles(Control *p_control, bool scaled = true) { if (scaled) { @@ -131,6 +135,7 @@ public: } else { base_tiles_root_control->add_child(p_control); } + p_control->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); p_control->set_mouse_filter(Control::MOUSE_FILTER_PASS); }; @@ -144,13 +149,14 @@ public: } else { alternative_tiles_root_control->add_child(p_control); } + p_control->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); p_control->set_mouse_filter(Control::MOUSE_FILTER_PASS); }; - // Update everything. - void update(); + // Redraw everything. + void queue_redraw(); TileAtlasView(); }; -#endif // TILE_ATLAS_VIEW +#endif // TILE_ATLAS_VIEW_H diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 73fd62d2c4..3dd0c84ee7 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* tile_data_editors.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_data_editors.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 "tile_data_editors.h" @@ -35,12 +35,23 @@ #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_properties.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" + +#include "scene/gui/menu_button.h" +#include "scene/gui/option_button.h" +#include "scene/gui/separator.h" + +#ifdef DEBUG_ENABLED +#include "servers/navigation_server_3d.h" +#endif // DEBUG_ENABLED void TileDataEditor::_tile_set_changed_plan_update() { _tile_set_changed_update_needed = true; - call_deferred("_tile_set_changed_deferred_update"); + call_deferred(SNAME("_tile_set_changed_deferred_update")); } void TileDataEditor::_tile_set_changed_deferred_update() { @@ -60,7 +71,7 @@ TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) { if (atlas_source) { ERR_FAIL_COND_V(!atlas_source->has_tile(p_cell.get_atlas_coords()), nullptr); ERR_FAIL_COND_V(!atlas_source->has_alternative_tile(p_cell.get_atlas_coords(), p_cell.alternative_tile), nullptr); - td = Object::cast_to<TileData>(atlas_source->get_tile_data(p_cell.get_atlas_coords(), p_cell.alternative_tile)); + td = atlas_source->get_tile_data(p_cell.get_atlas_coords(), p_cell.alternative_tile); } return td; @@ -122,7 +133,7 @@ void GenericTilePolygonEditor::_base_control_draw() { real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); const Ref<Texture2D> handle = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons")); const Ref<Texture2D> add_handle = get_theme_icon(SNAME("EditorHandleAdd"), SNAME("EditorIcons")); const Ref<StyleBox> focus_stylebox = get_theme_stylebox(SNAME("Focus"), SNAME("EditorStyles")); @@ -147,12 +158,18 @@ void GenericTilePolygonEditor::_base_control_draw() { // Draw the background. if (background_texture.is_valid()) { - base_control->draw_texture_rect_region(background_texture, Rect2(-background_region.size / 2 - background_offset, background_region.size), background_region, background_modulate, background_transpose); + Size2 region_size = background_region.size; + if (background_h_flip) { + region_size.x = -region_size.x; + } + if (background_v_flip) { + region_size.y = -region_size.y; + } + base_control->draw_texture_rect_region(background_texture, Rect2(-background_region.size / 2 - background_offset, region_size), background_region, background_modulate, background_transpose); } // Draw the polygons. - for (unsigned int i = 0; i < polygons.size(); i++) { - const Vector<Vector2> &polygon = polygons[i]; + for (const Vector<Vector2> &polygon : polygons) { Color color = polygon_color; if (!in_creation_polygon.is_empty()) { color = color.darkened(0.3); @@ -206,8 +223,8 @@ void GenericTilePolygonEditor::_base_control_draw() { for (int i = 0; i < (int)polygons.size(); i++) { const Vector<Vector2> &polygon = polygons[i]; for (int j = 0; j < polygon.size(); j++) { - const Color modulate = (tinted_polygon_index == i && tinted_point_index == j) ? Color(0.5, 1, 2) : Color(1, 1, 1); - base_control->draw_texture(handle, xform.xform(polygon[j]) - handle->get_size() / 2, modulate); + const Color poly_modulate = (tinted_polygon_index == i && tinted_point_index == j) ? Color(0.5, 1, 2) : Color(1, 1, 1); + base_control->draw_texture(handle, xform.xform(polygon[j]) - handle->get_size() / 2, poly_modulate); } } } @@ -217,7 +234,7 @@ void GenericTilePolygonEditor::_base_control_draw() { Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); String text = multiple_polygon_mode ? vformat("%d:%d", tinted_polygon_index, tinted_point_index) : vformat("%d", tinted_point_index); - Size2 text_size = font->get_string_size(text, font_size); + Size2 text_size = font->get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); base_control->draw_string(font, xform.xform(polygons[tinted_polygon_index][tinted_point_index]) - text_size * 0.5, text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5)); } @@ -238,16 +255,23 @@ void GenericTilePolygonEditor::_base_control_draw() { void GenericTilePolygonEditor::_center_view() { panning = Vector2(); - base_control->update(); + base_control->queue_redraw(); button_center_view->set_disabled(true); } void GenericTilePolygonEditor::_zoom_changed() { - base_control->update(); + base_control->queue_redraw(); } void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { - UndoRedo *undo_redo = use_undo_redo ? editor_undo_redo : memnew(UndoRedo); + EditorUndoRedoManager *undo_redo; + if (use_undo_redo) { + undo_redo = EditorUndoRedoManager::get_singleton(); + } else { + // This nice hack allows for discarding undo actions without making code too complex. + undo_redo = memnew(EditorUndoRedoManager); + } + switch (p_item_pressed) { case RESET_TO_DEFAULT_TILE: { undo_redo->create_action(TTR("Reset Polygons")); @@ -257,26 +281,26 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { polygon.write[i] = polygon[i] * tile_set->get_tile_size(); } undo_redo->add_do_method(this, "add_polygon", polygon); - undo_redo->add_do_method(base_control, "update"); + undo_redo->add_do_method(base_control, "queue_redraw"); undo_redo->add_do_method(this, "emit_signal", "polygons_changed"); undo_redo->add_undo_method(this, "clear_polygons"); - for (unsigned int i = 0; i < polygons.size(); i++) { - undo_redo->add_undo_method(this, "add_polygon", polygons[i]); + for (const PackedVector2Array &poly : polygons) { + undo_redo->add_undo_method(this, "add_polygon", poly); } - undo_redo->add_undo_method(base_control, "update"); + undo_redo->add_undo_method(base_control, "queue_redraw"); undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); undo_redo->commit_action(true); } break; case CLEAR_TILE: { undo_redo->create_action(TTR("Clear Polygons")); undo_redo->add_do_method(this, "clear_polygons"); - undo_redo->add_do_method(base_control, "update"); + undo_redo->add_do_method(base_control, "queue_redraw"); undo_redo->add_do_method(this, "emit_signal", "polygons_changed"); undo_redo->add_undo_method(this, "clear_polygons"); - for (unsigned int i = 0; i < polygons.size(); i++) { - undo_redo->add_undo_method(this, "add_polygon", polygons[i]); + for (const PackedVector2Array &polygon : polygons) { + undo_redo->add_undo_method(this, "add_polygon", polygon); } - undo_redo->add_undo_method(base_control, "update"); + undo_redo->add_undo_method(base_control, "queue_redraw"); undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); undo_redo->commit_action(true); } break; @@ -284,11 +308,26 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { case ROTATE_LEFT: case FLIP_HORIZONTALLY: case FLIP_VERTICALLY: { - undo_redo->create_action(TTR("Rotate Polygons Left")); + switch (p_item_pressed) { + case ROTATE_RIGHT: { + undo_redo->create_action(TTR("Rotate Polygons Right")); + } break; + case ROTATE_LEFT: { + undo_redo->create_action(TTR("Rotate Polygons Left")); + } break; + case FLIP_HORIZONTALLY: { + undo_redo->create_action(TTR("Flip Polygons Horizontally")); + } break; + case FLIP_VERTICALLY: { + undo_redo->create_action(TTR("Flip Polygons Vertically")); + } break; + default: + break; + } for (unsigned int i = 0; i < polygons.size(); i++) { Vector<Point2> new_polygon; - for (int point_index = 0; point_index < polygons[i].size(); point_index++) { - Vector2 point = polygons[i][point_index]; + for (const Vector2 &vec : polygons[i]) { + Vector2 point = vec; switch (p_item_pressed) { case ROTATE_RIGHT: { point = Vector2(-point.y, point.x); @@ -309,18 +348,19 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { } undo_redo->add_do_method(this, "set_polygon", i, new_polygon); } - undo_redo->add_do_method(base_control, "update"); + undo_redo->add_do_method(base_control, "queue_redraw"); undo_redo->add_do_method(this, "emit_signal", "polygons_changed"); - for (unsigned int i = 0; i < polygons.size(); i++) { - undo_redo->add_undo_method(this, "set_polygon", polygons[i]); + for (const PackedVector2Array &polygon : polygons) { + undo_redo->add_undo_method(this, "set_polygon", polygon); } - undo_redo->add_undo_method(base_control, "update"); + undo_redo->add_undo_method(base_control, "queue_redraw"); undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); undo_redo->commit_action(true); } break; default: break; } + if (!use_undo_redo) { memdelete(undo_redo); } @@ -408,7 +448,14 @@ void GenericTilePolygonEditor::_snap_to_half_pixel(Point2 &r_point) { } void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) { - UndoRedo *undo_redo = use_undo_redo ? editor_undo_redo : memnew(UndoRedo); + EditorUndoRedoManager *undo_redo; + if (use_undo_redo) { + undo_redo = EditorUndoRedoManager::get_singleton(); + } else { + // This nice hack allows for discarding undo actions without making code too complex. + undo_redo = memnew(EditorUndoRedoManager); + } + real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); hovered_polygon_index = -1; @@ -435,7 +482,7 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) } else if (drag_type == DRAG_TYPE_PAN) { panning += mm->get_position() - drag_last_pos; drag_last_pos = mm->get_position(); - button_center_view->set_disabled(panning.is_equal_approx(Vector2())); + button_center_view->set_disabled(panning.is_zero_approx()); } else { // Update hovered point. _grab_polygon_point(mm->get_position(), xform, hovered_polygon_index, hovered_point_index); @@ -449,11 +496,11 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_ctrl_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_command_or_control_pressed()) { editor_zoom_widget->set_zoom_by_increments(1); _zoom_changed(); accept_event(); - } else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_ctrl_pressed()) { + } else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_command_or_control_pressed()) { editor_zoom_widget->set_zoom_by_increments(-1); _zoom_changed(); accept_event(); @@ -478,9 +525,9 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) undo_redo->add_do_method(this, "clear_polygons"); } undo_redo->add_do_method(this, "add_polygon", in_creation_polygon); - undo_redo->add_do_method(base_control, "update"); + undo_redo->add_do_method(base_control, "queue_redraw"); undo_redo->add_undo_method(this, "remove_polygon", added); - undo_redo->add_undo_method(base_control, "update"); + undo_redo->add_undo_method(base_control, "queue_redraw"); undo_redo->commit_action(false); emit_signal(SNAME("polygons_changed")); } else { @@ -526,8 +573,8 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) undo_redo->add_do_method(this, "set_polygon", closest_polygon, polygons[closest_polygon]); undo_redo->add_undo_method(this, "set_polygon", closest_polygon, old_polygon); } - undo_redo->add_do_method(base_control, "update"); - undo_redo->add_undo_method(base_control, "update"); + undo_redo->add_do_method(base_control, "queue_redraw"); + undo_redo->add_undo_method(base_control, "queue_redraw"); undo_redo->commit_action(false); emit_signal(SNAME("polygons_changed")); } @@ -536,9 +583,9 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) if (drag_type == DRAG_TYPE_DRAG_POINT) { undo_redo->create_action(TTR("Edit Polygons")); undo_redo->add_do_method(this, "set_polygon", drag_polygon_index, polygons[drag_polygon_index]); - undo_redo->add_do_method(base_control, "update"); + undo_redo->add_do_method(base_control, "queue_redraw"); undo_redo->add_undo_method(this, "set_polygon", drag_polygon_index, drag_old_polygon); - undo_redo->add_undo_method(base_control, "update"); + undo_redo->add_undo_method(base_control, "queue_redraw"); undo_redo->commit_action(false); emit_signal(SNAME("polygons_changed")); } else if (drag_type == DRAG_TYPE_CREATE_POINT) { @@ -573,8 +620,8 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) undo_redo->add_do_method(this, "set_polygon", closest_polygon, polygons[closest_polygon]); undo_redo->add_undo_method(this, "set_polygon", closest_polygon, old_polygon); } - undo_redo->add_do_method(base_control, "update"); - undo_redo->add_undo_method(base_control, "update"); + undo_redo->add_do_method(base_control, "queue_redraw"); + undo_redo->add_undo_method(base_control, "queue_redraw"); undo_redo->commit_action(false); emit_signal(SNAME("polygons_changed")); } else { @@ -598,7 +645,7 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) } } - base_control->update(); + base_control->queue_redraw(); if (!use_undo_redo) { memdelete(undo_redo); @@ -650,7 +697,7 @@ void GenericTilePolygonEditor::set_background(Ref<Texture2D> p_texture, Rect2 p_ background_v_flip = p_flip_v; background_transpose = p_transpose; background_modulate = p_modulate; - base_control->update(); + base_control->queue_redraw(); } int GenericTilePolygonEditor::get_polygon_count() { @@ -663,13 +710,13 @@ int GenericTilePolygonEditor::add_polygon(Vector<Point2> p_polygon, int p_index) if (p_index < 0) { polygons.push_back(p_polygon); - base_control->update(); + base_control->queue_redraw(); button_edit->set_pressed(true); return polygons.size() - 1; } else { polygons.insert(p_index, p_polygon); button_edit->set_pressed(true); - base_control->update(); + base_control->queue_redraw(); return p_index; } } @@ -681,12 +728,12 @@ void GenericTilePolygonEditor::remove_polygon(int p_index) { if (polygons.size() == 0) { button_create->set_pressed(true); } - base_control->update(); + base_control->queue_redraw(); } void GenericTilePolygonEditor::clear_polygons() { polygons.clear(); - base_control->update(); + base_control->queue_redraw(); } void GenericTilePolygonEditor::set_polygon(int p_polygon_index, Vector<Point2> p_polygon) { @@ -694,7 +741,7 @@ void GenericTilePolygonEditor::set_polygon(int p_polygon_index, Vector<Point2> p ERR_FAIL_COND(p_polygon.size() < 3); polygons[p_polygon_index] = p_polygon; button_edit->set_pressed(true); - base_control->update(); + base_control->queue_redraw(); } Vector<Point2> GenericTilePolygonEditor::get_polygon(int p_polygon_index) { @@ -704,7 +751,7 @@ Vector<Point2> GenericTilePolygonEditor::get_polygon(int p_polygon_index) { void GenericTilePolygonEditor::set_polygons_color(Color p_color) { polygon_color = p_color; - base_control->update(); + base_control->queue_redraw(); } void GenericTilePolygonEditor::set_multiple_polygon_mode(bool p_multiple_polygon_mode) { @@ -713,20 +760,21 @@ void GenericTilePolygonEditor::set_multiple_polygon_mode(bool p_multiple_polygon void GenericTilePolygonEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: - button_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); - button_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); - button_delete->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); - button_center_view->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons"))); - button_pixel_snap->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); - button_advanced_menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + button_create->set_icon(get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); + button_edit->set_icon(get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); + button_delete->set_icon(get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); + button_center_view->set_icon(get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons"))); + button_pixel_snap->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); + button_advanced_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); PopupMenu *p = button_advanced_menu->get_popup(); p->set_item_icon(p->get_item_index(ROTATE_RIGHT), get_theme_icon(SNAME("RotateRight"), SNAME("EditorIcons"))); p->set_item_icon(p->get_item_index(ROTATE_LEFT), get_theme_icon(SNAME("RotateLeft"), SNAME("EditorIcons"))); p->set_item_icon(p->get_item_index(FLIP_HORIZONTALLY), get_theme_icon(SNAME("MirrorX"), SNAME("EditorIcons"))); p->set_item_icon(p->get_item_index(FLIP_VERTICALLY), get_theme_icon(SNAME("MirrorY"), SNAME("EditorIcons"))); - break; + } break; } } @@ -752,33 +800,33 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { button_create->set_toggle_mode(true); button_create->set_button_group(tools_button_group); button_create->set_pressed(true); - button_create->set_tooltip(TTR("Add polygon tool")); + button_create->set_tooltip_text(TTR("Add polygon tool")); toolbar->add_child(button_create); button_edit = memnew(Button); button_edit->set_flat(true); button_edit->set_toggle_mode(true); button_edit->set_button_group(tools_button_group); - button_edit->set_tooltip(TTR("Edit points tool")); + button_edit->set_tooltip_text(TTR("Edit points tool")); toolbar->add_child(button_edit); button_delete = memnew(Button); button_delete->set_flat(true); button_delete->set_toggle_mode(true); button_delete->set_button_group(tools_button_group); - button_delete->set_tooltip(TTR("Delete points tool")); + button_delete->set_tooltip_text(TTR("Delete points tool")); toolbar->add_child(button_delete); button_advanced_menu = memnew(MenuButton); button_advanced_menu->set_flat(true); button_advanced_menu->set_toggle_mode(true); - button_advanced_menu->get_popup()->add_item(TTR("Reset to default tile shape"), RESET_TO_DEFAULT_TILE); - button_advanced_menu->get_popup()->add_item(TTR("Clear"), CLEAR_TILE); + button_advanced_menu->get_popup()->add_item(TTR("Reset to default tile shape"), RESET_TO_DEFAULT_TILE, Key::F); + button_advanced_menu->get_popup()->add_item(TTR("Clear"), CLEAR_TILE, Key::C); button_advanced_menu->get_popup()->add_separator(); - button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("RotateRight"), SNAME("EditorIcons")), TTR("Rotate Right"), ROTATE_RIGHT); - button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("RotateLeft"), SNAME("EditorIcons")), TTR("Rotate Left"), ROTATE_LEFT); - button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("MirrorX"), SNAME("EditorIcons")), TTR("Flip Horizontally"), FLIP_HORIZONTALLY); - button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("MirrorY"), SNAME("EditorIcons")), TTR("Flip Vertically"), FLIP_VERTICALLY); + button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("RotateRight"), SNAME("EditorIcons")), TTR("Rotate Right"), ROTATE_RIGHT, Key::R); + button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("RotateLeft"), SNAME("EditorIcons")), TTR("Rotate Left"), ROTATE_LEFT, Key::E); + button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("MirrorX"), SNAME("EditorIcons")), TTR("Flip Horizontally"), FLIP_HORIZONTALLY, Key::H); + button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("MirrorY"), SNAME("EditorIcons")), TTR("Flip Vertically"), FLIP_VERTICALLY, Key::V); button_advanced_menu->get_popup()->connect("id_pressed", callable_mp(this, &GenericTilePolygonEditor::_advanced_menu_item_pressed)); button_advanced_menu->set_focus_mode(FOCUS_ALL); toolbar->add_child(button_advanced_menu); @@ -789,7 +837,7 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { button_pixel_snap->set_flat(true); button_pixel_snap->set_toggle_mode(true); button_pixel_snap->set_pressed(true); - button_pixel_snap->set_tooltip(TTR("Snap to half-pixel")); + button_pixel_snap->set_tooltip_text(TTR("Snap to half-pixel")); toolbar->add_child(button_pixel_snap); Control *root = memnew(Control); @@ -799,13 +847,13 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { add_child(root); panel = memnew(Panel); - panel->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); root->add_child(panel); base_control = memnew(Control); base_control->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); - base_control->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + base_control->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); base_control->connect("draw", callable_mp(this, &GenericTilePolygonEditor::_base_control_draw)); base_control->connect("gui_input", callable_mp(this, &GenericTilePolygonEditor::_base_control_gui_input)); base_control->set_clip_contents(true); @@ -815,6 +863,7 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { editor_zoom_widget = memnew(EditorZoomWidget); editor_zoom_widget->set_position(Vector2(5, 5)); editor_zoom_widget->connect("zoom_changed", callable_mp(this, &GenericTilePolygonEditor::_zoom_changed).unbind(1)); + editor_zoom_widget->set_shortcut_context(this); root->add_child(editor_zoom_widget); button_center_view = memnew(Button); @@ -829,6 +878,7 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { void TileDataDefaultEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) { ERR_FAIL_COND(!dummy_object); dummy_object->set(p_property, p_value); + emit_signal(SNAME("needs_redraw")); } Variant TileDataDefaultEditor::_get_painted_value() { @@ -837,7 +887,7 @@ Variant TileDataDefaultEditor::_get_painted_value() { } void TileDataDefaultEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); Variant value = tile_data->get(property); dummy_object->set(property, value); @@ -847,18 +897,19 @@ void TileDataDefaultEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_at } void TileDataDefaultEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); tile_data->set(property, p_value); } Variant TileDataDefaultEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND_V(!tile_data, Variant()); return tile_data->get(property); } -void TileDataDefaultEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { +void TileDataDefaultEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { Vector2i coords = E.key.get_atlas_coords(); undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E.key.alternative_tile, property), E.value); @@ -868,17 +919,17 @@ void TileDataDefaultEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_s void TileDataDefaultEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) { if (drag_type == DRAG_TYPE_PAINT_RECT) { - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); p_canvas_item->draw_set_transform_matrix(p_transform); Rect2i rect; - rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); - rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()))); + rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos, true)); + rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()), true)); rect = rect.abs(); - Set<TileMapCell> edited; + RBSet<TileMapCell> edited; for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); @@ -893,8 +944,8 @@ void TileDataDefaultEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas_ } } - for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { - Vector2i coords = E->get().get_atlas_coords(); + for (const TileMapCell &E : edited) { + Vector2i coords = E.get_atlas_coords(); p_canvas_item->draw_rect(p_tile_set_atlas_source->get_tile_texture_region(coords), selection_color, false); } p_canvas_item->draw_set_transform_matrix(Transform2D()); @@ -909,7 +960,7 @@ void TileDataDefaultEditor::forward_painting_atlas_gui_input(TileAtlasView *p_ti Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { if (drag_type == DRAG_TYPE_PAINT) { - Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position())); + Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos, true), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position(), true)); for (int i = 0; i < line.size(); i++) { Vector2i coords = p_tile_set_atlas_source->get_tile_at_coords(line[i]); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { @@ -927,18 +978,19 @@ void TileDataDefaultEditor::forward_painting_atlas_gui_input(TileAtlasView *p_ti } } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { - if (picker_button->is_pressed()) { - Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); + if (picker_button->is_pressed() || (mb->is_command_or_control_pressed() && !mb->is_shift_pressed())) { + Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position(), true); coords = p_tile_set_atlas_source->get_tile_at_coords(coords); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { _set_painted_value(p_tile_set_atlas_source, coords, 0); picker_button->set_pressed(false); } - } else if (mb->is_ctrl_pressed()) { + } else if (mb->is_command_or_control_pressed() && mb->is_shift_pressed()) { drag_type = DRAG_TYPE_PAINT_RECT; drag_modified.clear(); drag_painted_value = _get_painted_value(); @@ -947,7 +999,7 @@ void TileDataDefaultEditor::forward_painting_atlas_gui_input(TileAtlasView *p_ti drag_type = DRAG_TYPE_PAINT; drag_modified.clear(); drag_painted_value = _get_painted_value(); - Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); + Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position(), true); coords = p_tile_set_atlas_source->get_tile_at_coords(coords); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { TileMapCell cell; @@ -962,8 +1014,8 @@ void TileDataDefaultEditor::forward_painting_atlas_gui_input(TileAtlasView *p_ti } else { if (drag_type == DRAG_TYPE_PAINT_RECT) { Rect2i rect; - rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); - rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position())); + rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos, true)); + rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position(), true)); rect = rect.abs(); drag_modified.clear(); @@ -1050,6 +1102,7 @@ void TileDataDefaultEditor::forward_painting_alternatives_gui_input(TileAtlasVie drag_last_pos = mb->get_position(); } } else { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Painting Tiles Property")); _setup_undo_redo_action(p_tile_set_atlas_source, drag_modified, drag_painted_value); undo_redo->commit_action(false); @@ -1069,14 +1122,15 @@ void TileDataDefaultEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2 return; } + Vector2 texture_origin = tile_data->get_texture_origin(); if (value.get_type() == Variant::BOOL) { Ref<Texture2D> texture = (bool)value ? tile_bool_checked : tile_bool_unchecked; int size = MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 3; - Rect2 rect = p_transform.xform(Rect2(Vector2(-size / 2, -size / 2), Vector2(size, size))); + Rect2 rect = p_transform.xform(Rect2(Vector2(-size / 2, -size / 2) - texture_origin, Vector2(size, size))); p_canvas_item->draw_texture_rect(texture, rect); } else if (value.get_type() == Variant::COLOR) { int size = MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 3; - Rect2 rect = p_transform.xform(Rect2(Vector2(-size / 2, -size / 2), Vector2(size, size))); + Rect2 rect = p_transform.xform(Rect2(Vector2(-size / 2, -size / 2) - texture_origin, Vector2(size, size))); p_canvas_item->draw_rect(rect, value); } else { Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font(SNAME("bold"), SNAME("EditorFonts")); @@ -1100,7 +1154,7 @@ void TileDataDefaultEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2 Color color = Color(1, 1, 1); if (p_selected) { - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); selection_color.set_v(0.9); color = selection_color; @@ -1112,18 +1166,20 @@ void TileDataDefaultEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2 } } - Vector2 string_size = font->get_string_size(text, font_size); - p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color, 1, Color(0, 0, 0, 1)); + Vector2 string_size = font->get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); + p_canvas_item->draw_string_outline(font, p_transform.xform(-texture_origin) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, 1, Color(0, 0, 0, 1)); + p_canvas_item->draw_string(font, p_transform.xform(-texture_origin) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color); } } void TileDataDefaultEditor::setup_property_editor(Variant::Type p_type, String p_property, String p_label, Variant p_default_value) { ERR_FAIL_COND_MSG(!property.is_empty(), "Cannot setup TileDataDefaultEditor twice"); property = p_property; + property_type = p_type; // Update everything. if (property_editor) { - property_editor->queue_delete(); + property_editor->queue_free(); } // Update the dummy object. @@ -1143,11 +1199,12 @@ void TileDataDefaultEditor::setup_property_editor(Variant::Type p_type, String p property_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, p_type, p_property, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); property_editor->set_object_and_property(dummy_object, p_property); if (p_label.is_empty()) { - property_editor->set_label(p_property); + property_editor->set_label(EditorPropertyNameProcessor::get_singleton()->process_name(p_property, EditorPropertyNameProcessor::get_default_inspector_style())); } else { property_editor->set_label(p_label); } property_editor->connect("property_changed", callable_mp(this, &TileDataDefaultEditor::_property_value_changed).unbind(1)); + property_editor->set_tooltip_text(p_property); property_editor->update_property(); add_child(property_editor); } @@ -1155,23 +1212,24 @@ void TileDataDefaultEditor::setup_property_editor(Variant::Type p_type, String p void TileDataDefaultEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); tile_bool_checked = get_theme_icon(SNAME("TileChecked"), SNAME("EditorIcons")); tile_bool_unchecked = get_theme_icon(SNAME("TileUnchecked"), SNAME("EditorIcons")); - break; - default: - break; + } break; } } +Variant::Type TileDataDefaultEditor::get_property_type() { + return property_type; +} + TileDataDefaultEditor::TileDataDefaultEditor() { label = memnew(Label); label->set_text(TTR("Painting:")); + label->set_theme_type_variation("HeaderSmall"); add_child(label); - toolbar->add_child(memnew(VSeparator)); - picker_button = memnew(Button); picker_button->set_flat(true); picker_button->set_toggle_mode(true); @@ -1180,24 +1238,42 @@ TileDataDefaultEditor::TileDataDefaultEditor() { } TileDataDefaultEditor::~TileDataDefaultEditor() { - toolbar->queue_delete(); + toolbar->queue_free(); memdelete(dummy_object); } -void TileDataTextureOffsetEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { +void TileDataTextureOriginEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { TileData *tile_data = _get_tile_data(p_cell); ERR_FAIL_COND(!tile_data); Vector2i tile_set_tile_size = tile_set->get_tile_size(); - Color color = Color(1.0, 0.0, 0.0); + Color color = Color(1.0, 1.0, 1.0); if (p_selected) { - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); color = selection_color; } - Transform2D tile_xform; - tile_xform.set_scale(tile_set_tile_size); - tile_set->draw_tile_shape(p_canvas_item, p_transform * tile_xform, color); + + TileSetSource *source = *(tile_set->get_source(p_cell.source_id)); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source->is_position_in_tile_texture_region(p_cell.get_atlas_coords(), p_cell.alternative_tile, -tile_set_tile_size / 2) && atlas_source->is_position_in_tile_texture_region(p_cell.get_atlas_coords(), p_cell.alternative_tile, tile_set_tile_size / 2 - Vector2(1, 1))) { + Transform2D tile_xform; + tile_xform.set_scale(tile_set_tile_size); + tile_set->draw_tile_shape(p_canvas_item, p_transform * tile_xform, color); + } + + if (atlas_source->is_position_in_tile_texture_region(p_cell.get_atlas_coords(), p_cell.alternative_tile, Vector2())) { + Ref<Texture2D> position_icon = TileSetEditor::get_singleton()->get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")); + p_canvas_item->draw_texture(position_icon, p_transform.xform(Vector2()) - (position_icon->get_size() / 2), color); + } else { + Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = TileSetEditor::get_singleton()->get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); + Vector2 texture_origin = tile_data->get_texture_origin(); + String text = vformat("%s", texture_origin); + Vector2 string_size = font->get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); + p_canvas_item->draw_string_outline(font, p_transform.xform(-texture_origin) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, 1, Color(0, 0, 0, 1)); + p_canvas_item->draw_string(font, p_transform.xform(-texture_origin) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color); + } } void TileDataPositionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { @@ -1213,7 +1289,7 @@ void TileDataPositionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform Color color = Color(1.0, 1.0, 1.0); if (p_selected) { - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); color = selection_color; } @@ -1227,19 +1303,32 @@ void TileDataYSortEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D Color color = Color(1.0, 1.0, 1.0); if (p_selected) { - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); color = selection_color; } - Ref<Texture2D> position_icon = TileSetEditor::get_singleton()->get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")); - p_canvas_item->draw_texture(position_icon, p_transform.xform(Vector2(0, tile_data->get_y_sort_origin())) - position_icon->get_size() / 2, color); + Vector2 texture_origin = tile_data->get_texture_origin(); + TileSetSource *source = *(tile_set->get_source(p_cell.source_id)); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source->is_position_in_tile_texture_region(p_cell.get_atlas_coords(), p_cell.alternative_tile, Vector2(0, tile_data->get_y_sort_origin()))) { + Ref<Texture2D> position_icon = TileSetEditor::get_singleton()->get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")); + p_canvas_item->draw_texture(position_icon, p_transform.xform(Vector2(0, tile_data->get_y_sort_origin())) - position_icon->get_size() / 2, color); + } else { + Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = TileSetEditor::get_singleton()->get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); + String text = vformat("%s", tile_data->get_y_sort_origin()); + + Vector2 string_size = font->get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); + p_canvas_item->draw_string_outline(font, p_transform.xform(-texture_origin) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, 1, Color(0, 0, 0, 1)); + p_canvas_item->draw_string(font, p_transform.xform(-texture_origin) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color); + } } void TileDataOcclusionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { TileData *tile_data = _get_tile_data(p_cell); ERR_FAIL_COND(!tile_data); - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); Color color = grid_color.darkened(0.2); if (p_selected) { @@ -1268,7 +1357,7 @@ Variant TileDataOcclusionShapeEditor::_get_painted_value() { } void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer); @@ -1276,25 +1365,26 @@ void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile if (occluder_polygon.is_valid()) { polygon_editor->add_polygon(occluder_polygon->get_polygon()); } - polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); + polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); } void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); Ref<OccluderPolygon2D> occluder_polygon = p_value; tile_data->set_occluder(occlusion_layer, occluder_polygon); - polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); + polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); } Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND_V(!tile_data, Variant()); return tile_data->get_occluder(occlusion_layer); } -void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { +void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { Vector2i coords = E.key.get_atlas_coords(); undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E.key.alternative_tile, occlusion_layer), E.value); @@ -1308,11 +1398,9 @@ void TileDataOcclusionShapeEditor::_tile_set_changed() { void TileDataOcclusionShapeEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_ENTER_TREE: { polygon_editor->set_polygons_color(get_tree()->get_debug_collisions_color()); - break; - default: - break; + } break; } } @@ -1325,6 +1413,15 @@ void TileDataCollisionEditor::_property_value_changed(StringName p_property, Var dummy_object->set(p_property, p_value); } +void TileDataCollisionEditor::_property_selected(StringName p_path, int p_focusable) { + // Deselect all other properties + for (KeyValue<StringName, EditorProperty *> &editor : property_editors) { + if (editor.key != p_path) { + editor.value->deselect(); + } + } +} + void TileDataCollisionEditor::_polygons_changed() { // Update the dummy object properties and their editors. for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { @@ -1346,6 +1443,8 @@ void TileDataCollisionEditor::_polygons_changed() { one_way_property_editor->set_object_and_property(dummy_object, one_way_property); one_way_property_editor->set_label(one_way_property); one_way_property_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + one_way_property_editor->connect("selected", callable_mp(this, &TileDataCollisionEditor::_property_selected)); + one_way_property_editor->set_tooltip_text(one_way_property_editor->get_edited_property()); one_way_property_editor->update_property(); add_child(one_way_property_editor); property_editors[one_way_property] = one_way_property_editor; @@ -1356,6 +1455,8 @@ void TileDataCollisionEditor::_polygons_changed() { one_way_margin_property_editor->set_object_and_property(dummy_object, one_way_margin_property); one_way_margin_property_editor->set_label(one_way_margin_property); one_way_margin_property_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + one_way_margin_property_editor->connect("selected", callable_mp(this, &TileDataCollisionEditor::_property_selected)); + one_way_margin_property_editor->set_tooltip_text(one_way_margin_property_editor->get_edited_property()); one_way_margin_property_editor->update_property(); add_child(one_way_margin_property_editor); property_editors[one_way_margin_property] = one_way_margin_property_editor; @@ -1370,11 +1471,11 @@ void TileDataCollisionEditor::_polygons_changed() { dummy_object->remove_dummy_property(vformat("polygon_%d_one_way_margin", i)); } for (int i = polygon_editor->get_polygon_count(); property_editors.has(vformat("polygon_%d_one_way", i)); i++) { - property_editors[vformat("polygon_%d_one_way", i)]->queue_delete(); + property_editors[vformat("polygon_%d_one_way", i)]->queue_free(); property_editors.erase(vformat("polygon_%d_one_way", i)); } for (int i = polygon_editor->get_polygon_count(); property_editors.has(vformat("polygon_%d_one_way_margin", i)); i++) { - property_editors[vformat("polygon_%d_one_way_margin", i)]->queue_delete(); + property_editors[vformat("polygon_%d_one_way_margin", i)]->queue_free(); property_editors.erase(vformat("polygon_%d_one_way_margin", i)); } } @@ -1398,7 +1499,7 @@ Variant TileDataCollisionEditor::_get_painted_value() { } void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); polygon_editor->clear_polygons(); @@ -1420,11 +1521,11 @@ void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_ E.value->update_property(); } - polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); + polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); } void TileDataCollisionEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); Dictionary dict = p_value; @@ -1439,11 +1540,11 @@ void TileDataCollisionEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_so tile_data->set_collision_polygon_one_way_margin(physics_layer, i, polygon_dict["one_way_margin"]); } - polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); + polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); } Variant TileDataCollisionEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND_V(!tile_data, Variant()); Dictionary dict; @@ -1461,26 +1562,33 @@ Variant TileDataCollisionEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas return dict; } -void TileDataCollisionEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { - Array new_array = p_new_value; +void TileDataCollisionEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) { + Dictionary new_dict = p_new_value; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); for (KeyValue<TileMapCell, Variant> &E : p_previous_values) { - Array old_array = E.value; - Vector2i coords = E.key.get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E.key.alternative_tile, physics_layer), old_array.size()); - for (int i = 0; i < old_array.size(); i++) { - Dictionary dict = old_array[i]; - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["points"]); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way"]); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way_margin"]); + + Dictionary old_dict = E.value; + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/linear_velocity", coords.x, coords.y, E.key.alternative_tile, physics_layer), old_dict["linear_velocity"]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/angular_velocity", coords.x, coords.y, E.key.alternative_tile, physics_layer), old_dict["angular_velocity"]); + Array old_polygon_array = old_dict["polygons"]; + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E.key.alternative_tile, physics_layer), old_polygon_array.size()); + for (int i = 0; i < old_polygon_array.size(); i++) { + Dictionary polygon_dict = old_polygon_array[i]; + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), polygon_dict["points"]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), polygon_dict["one_way"]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), polygon_dict["one_way_margin"]); } - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E.key.alternative_tile, physics_layer), new_array.size()); - for (int i = 0; i < new_array.size(); i++) { - Dictionary dict = new_array[i]; - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["points"]); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way"]); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way_margin"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/linear_velocity", coords.x, coords.y, E.key.alternative_tile, physics_layer), new_dict["linear_velocity"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/angular_velocity", coords.x, coords.y, E.key.alternative_tile, physics_layer), new_dict["angular_velocity"]); + Array new_polygon_array = new_dict["polygons"]; + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E.key.alternative_tile, physics_layer), new_polygon_array.size()); + for (int i = 0; i < new_polygon_array.size(); i++) { + Dictionary polygon_dict = new_polygon_array[i]; + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), polygon_dict["points"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), polygon_dict["one_way"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), polygon_dict["one_way_margin"]); } } } @@ -1492,11 +1600,9 @@ void TileDataCollisionEditor::_tile_set_changed() { void TileDataCollisionEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_ENTER_TREE: { polygon_editor->set_polygons_color(get_tree()->get_debug_collisions_color()); - break; - default: - break; + } break; } } @@ -1515,6 +1621,8 @@ TileDataCollisionEditor::TileDataCollisionEditor() { linear_velocity_editor->set_object_and_property(dummy_object, "linear_velocity"); linear_velocity_editor->set_label("linear_velocity"); linear_velocity_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + linear_velocity_editor->connect("selected", callable_mp(this, &TileDataCollisionEditor::_property_selected)); + linear_velocity_editor->set_tooltip_text(linear_velocity_editor->get_edited_property()); linear_velocity_editor->update_property(); add_child(linear_velocity_editor); property_editors["linear_velocity"] = linear_velocity_editor; @@ -1523,6 +1631,8 @@ TileDataCollisionEditor::TileDataCollisionEditor() { angular_velocity_editor->set_object_and_property(dummy_object, "angular_velocity"); angular_velocity_editor->set_label("angular_velocity"); angular_velocity_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + angular_velocity_editor->connect("selected", callable_mp(this, &TileDataCollisionEditor::_property_selected)); + angular_velocity_editor->set_tooltip_text(angular_velocity_editor->get_edited_property()); angular_velocity_editor->update_property(); add_child(angular_velocity_editor); property_editors["angular_velocity"] = angular_velocity_editor; @@ -1541,7 +1651,7 @@ void TileDataCollisionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transfor // Draw all shapes. Vector<Color> color; if (p_selected) { - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); selection_color.a = 0.7; color.push_back(selection_color); @@ -1551,12 +1661,31 @@ void TileDataCollisionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transfor } RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); + + Ref<Texture2D> one_way_icon = get_theme_icon(SNAME("OneWayTile"), SNAME("EditorIcons")); for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { Vector<Vector2> polygon = tile_data->get_collision_polygon_points(physics_layer, i); - if (polygon.size() >= 3) { - p_canvas_item->draw_polygon(polygon, color); + if (polygon.size() < 3) { + continue; + } + + p_canvas_item->draw_polygon(polygon, color); + + if (tile_data->is_collision_polygon_one_way(physics_layer, i)) { + PackedVector2Array uvs; + uvs.resize(polygon.size()); + Vector2 size_1 = Vector2(1, 1) / tile_set->get_tile_size(); + + for (int j = 0; j < polygon.size(); j++) { + uvs.write[j] = polygon[j] * size_1 + Vector2(0.5, 0.5); + } + + Vector<Color> color2; + color2.push_back(Color(1, 1, 1, 0.4)); + p_canvas_item->draw_polygon(polygon, color2, uvs, one_way_icon); } } + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); } @@ -1641,10 +1770,10 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas hovered_coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_pos); hovered_coords = p_tile_set_atlas_source->get_tile_at_coords(hovered_coords); if (hovered_coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(hovered_coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(hovered_coords, 0); int terrain_set = tile_data->get_terrain_set(); Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(hovered_coords); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(hovered_coords, 0); + Vector2i position = texture_region.get_center() + tile_data->get_texture_origin(); if (terrain_set >= 0 && terrain_set == int(dummy_object->get("terrain_set"))) { // Draw hovered bit. @@ -1654,10 +1783,15 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas Vector<Color> color; color.push_back(Color(1.0, 1.0, 1.0, 0.5)); + Vector<Vector2> polygon = tile_set->get_terrain_polygon(terrain_set); + if (Geometry2D::is_point_in_polygon(xform.affine_inverse().xform(mouse_pos), polygon)) { + p_canvas_item->draw_set_transform_matrix(p_transform * xform); + p_canvas_item->draw_polygon(polygon, color); + } for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { + polygon = tile_set->get_terrain_peering_bit_polygon(terrain_set, bit); if (Geometry2D::is_point_in_polygon(xform.affine_inverse().xform(mouse_pos), polygon)) { p_canvas_item->draw_set_transform_matrix(p_transform * xform); p_canvas_item->draw_polygon(polygon, color); @@ -1680,7 +1814,7 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas for (int i = 0; i < p_tile_set_atlas_source->get_tiles_count(); i++) { Vector2i coords = p_tile_set_atlas_source->get_tile_id(i); if (coords != hovered_coords) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); if (tile_data->get_terrain_set() != int(dummy_object->get("terrain_set"))) { // Dimming p_canvas_item->draw_set_transform_matrix(p_transform); @@ -1690,7 +1824,7 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas // Text p_canvas_item->draw_set_transform_matrix(Transform2D()); Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + tile_data->get_texture_origin(); Color color = Color(1, 1, 1); String text; @@ -1699,8 +1833,9 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas } else { text = "-"; } - Vector2 string_size = font->get_string_size(text, font_size); - p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color, 1, Color(0, 0, 0, 1)); + Vector2 string_size = font->get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); + p_canvas_item->draw_string_outline(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, 1, Color(0, 0, 0, 1)); + p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color); } } } @@ -1708,17 +1843,17 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET_RECT) { // Draw selection rectangle. - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); p_canvas_item->draw_set_transform_matrix(p_transform); Rect2i rect; - rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); - rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()))); + rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos, true)); + rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()), true)); rect = rect.abs(); - Set<TileMapCell> edited; + RBSet<TileMapCell> edited; for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); @@ -1733,8 +1868,8 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas } } - for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { - Vector2i coords = E->get().get_atlas_coords(); + for (const TileMapCell &E : edited) { + Vector2i coords = E.get_atlas_coords(); p_canvas_item->draw_rect(p_tile_set_atlas_source->get_tile_texture_region(coords), selection_color, false); } p_canvas_item->draw_set_transform_matrix(Transform2D()); @@ -1744,17 +1879,17 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas int terrain_set = int(painted["terrain_set"]); Rect2i rect; - rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); - rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()))); + rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos, true)); + rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()), true)); rect = rect.abs(); - Set<TileMapCell> edited; + RBSet<TileMapCell> edited; for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); coords = p_tile_set_atlas_source->get_tile_at_coords(coords); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); if (tile_data->get_terrain_set() == terrain_set) { TileMapCell cell; cell.source_id = 0; @@ -1778,16 +1913,25 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas p_canvas_item->draw_set_transform_matrix(p_transform); - for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { - Vector2i coords = E->get().get_atlas_coords(); + for (const TileMapCell &E : edited) { + Vector2i coords = E.get_atlas_coords(); Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_data(coords, 0)->get_texture_origin(); + + Vector<Vector2> polygon = tile_set->get_terrain_polygon(terrain_set); + for (int j = 0; j < polygon.size(); j++) { + polygon.write[j] += position; + } + if (!Geometry2D::intersect_polygons(polygon, mouse_pos_rect_polygon).is_empty()) { + // Draw terrain. + p_canvas_item->draw_polygon(polygon, color); + } for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { + polygon = tile_set->get_terrain_peering_bit_polygon(terrain_set, bit); for (int j = 0; j < polygon.size(); j++) { polygon.write[j] += position; } @@ -1815,10 +1959,10 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til hovered_coords = Vector2i(hovered.x, hovered.y); hovered_alternative = hovered.z; if (hovered_coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(hovered_coords, hovered_alternative)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(hovered_coords, hovered_alternative); int terrain_set = tile_data->get_terrain_set(); Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(hovered_coords, hovered_alternative); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(hovered_coords, hovered_alternative); + Vector2i position = texture_region.get_center() + tile_data->get_texture_origin(); if (terrain_set == int(dummy_object->get("terrain_set"))) { // Draw hovered bit. @@ -1828,10 +1972,16 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til Vector<Color> color; color.push_back(Color(1.0, 1.0, 1.0, 0.5)); + Vector<Vector2> polygon = tile_set->get_terrain_polygon(terrain_set); + if (Geometry2D::is_point_in_polygon(xform.affine_inverse().xform(mouse_pos), polygon)) { + p_canvas_item->draw_set_transform_matrix(p_transform * xform); + p_canvas_item->draw_polygon(polygon, color); + } + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { + polygon = tile_set->get_terrain_peering_bit_polygon(terrain_set, bit); if (Geometry2D::is_point_in_polygon(xform.affine_inverse().xform(mouse_pos), polygon)) { p_canvas_item->draw_set_transform_matrix(p_transform * xform); p_canvas_item->draw_polygon(polygon, color); @@ -1856,7 +2006,7 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til for (int j = 1; j < p_tile_set_atlas_source->get_alternative_tiles_count(coords); j++) { int alternative_tile = p_tile_set_atlas_source->get_alternative_tile_id(coords, j); if (coords != hovered_coords || alternative_tile != hovered_alternative) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, alternative_tile); if (tile_data->get_terrain_set() != int(dummy_object->get("terrain_set"))) { // Dimming p_canvas_item->draw_set_transform_matrix(p_transform); @@ -1866,7 +2016,7 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til // Text p_canvas_item->draw_set_transform_matrix(Transform2D()); Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + tile_data->get_texture_origin(); Color color = Color(1, 1, 1); String text; @@ -1875,8 +2025,9 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til } else { text = "-"; } - Vector2 string_size = font->get_string_size(text, font_size); - p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color, 1, Color(0, 0, 0, 1)); + Vector2 string_size = font->get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); + p_canvas_item->draw_string_outline(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, 1, Color(0, 0, 0, 1)); + p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color); } } } @@ -1889,7 +2040,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { - Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position())); + Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos, true), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position(), true)); for (int i = 0; i < line.size(); i++) { Vector2i coords = p_tile_set_atlas_source->get_tile_at_coords(line[i]); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { @@ -1900,14 +2051,15 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t cell.alternative_tile = 0; // Save the old terrain_set and terrains bits. - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); if (!drag_modified.has(cell)) { Dictionary dict; dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); Array array; for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); } dict["terrain_peering_bits"] = array; drag_modified[cell] = dict; @@ -1918,10 +2070,11 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t } } drag_last_pos = mm->get_position(); + accept_event(); } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { int terrain_set = Dictionary(drag_painted_value)["terrain_set"]; int terrain = Dictionary(drag_painted_value)["terrain"]; - Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position())); + Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos, true), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position(), true)); for (int i = 0; i < line.size(); i++) { Vector2i coords = p_tile_set_atlas_source->get_tile_at_coords(line[i]); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { @@ -1930,16 +2083,17 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t cell.set_atlas_coords(coords); cell.alternative_tile = 0; - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); if (tile_data->get_terrain_set() == terrain_set) { // Save the old terrain_set and terrains bits. if (!drag_modified.has(cell)) { Dictionary dict; dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); Array array; for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); } dict["terrain_peering_bits"] = array; drag_modified[cell] = dict; @@ -1947,13 +2101,18 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t // Set the terrains bits. Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + tile_data->get_texture_origin(); + + Vector<Vector2> polygon = tile_set->get_terrain_polygon(tile_data->get_terrain_set()); + if (Geometry2D::is_segment_intersecting_polygon(mm->get_position() - position, drag_last_pos - position, polygon)) { + tile_data->set_terrain(terrain); + } for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - if (tile_data->is_valid_peering_bit_terrain(bit)) { - Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(tile_data->get_terrain_set(), bit); + if (tile_data->is_valid_terrain_peering_bit(bit)) { + polygon = tile_set->get_terrain_peering_bit_polygon(tile_data->get_terrain_set(), bit); if (Geometry2D::is_segment_intersecting_polygon(mm->get_position() - position, drag_last_pos - position, polygon)) { - tile_data->set_peering_bit_terrain(bit, terrain); + tile_data->set_terrain_peering_bit(bit, terrain); } } } @@ -1961,47 +2120,58 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t } } drag_last_pos = mm->get_position(); + accept_event(); } } Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MouseButton::LEFT) { + if (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT) { if (mb->is_pressed()) { - if (picker_button->is_pressed()) { + if (picker_button->is_pressed() || (mb->is_command_or_control_pressed() && !mb->is_shift_pressed())) { Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); coords = p_tile_set_atlas_source->get_tile_at_coords(coords); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); int terrain_set = tile_data->get_terrain_set(); Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + tile_data->get_texture_origin(); dummy_object->set("terrain_set", terrain_set); dummy_object->set("terrain", -1); + + Vector<Vector2> polygon = tile_set->get_terrain_polygon(terrain_set); + if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { + dummy_object->set("terrain", tile_data->get_terrain()); + } for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { + polygon = tile_set->get_terrain_peering_bit_polygon(terrain_set, bit); if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { - dummy_object->set("terrain", tile_data->get_peering_bit_terrain(bit)); + dummy_object->set("terrain", tile_data->get_terrain_peering_bit(bit)); } } } terrain_set_property_editor->update_property(); _update_terrain_selector(); picker_button->set_pressed(false); + accept_event(); } } else { Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); coords = p_tile_set_atlas_source->get_tile_at_coords(coords); TileData *tile_data = nullptr; if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { - tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); } int terrain_set = int(dummy_object->get("terrain_set")); int terrain = int(dummy_object->get("terrain")); if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) { - if (mb->is_ctrl_pressed()) { + // Paint terrain sets. + if (mb->get_button_index() == MouseButton::RIGHT) { + terrain_set = -1; + } + if (mb->is_command_or_control_pressed() && mb->is_shift_pressed()) { // Paint terrain set with rect. drag_type = DRAG_TYPE_PAINT_TERRAIN_SET_RECT; drag_modified.clear(); @@ -2022,10 +2192,11 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t // Save the old terrain_set and terrains bits. Dictionary dict; dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); Array array; for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); } dict["terrain_peering_bits"] = array; drag_modified[cell] = dict; @@ -2035,9 +2206,14 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t } drag_last_pos = mb->get_position(); } - } else if (tile_data && tile_data->get_terrain_set() == terrain_set) { - if (mb->is_ctrl_pressed()) { - // Paint terrain set with rect. + accept_event(); + } else if (tile_data->get_terrain_set() == terrain_set) { + // Paint terrain bits. + if (mb->get_button_index() == MouseButton::RIGHT) { + terrain = -1; + } + if (mb->is_command_or_control_pressed() && mb->is_shift_pressed()) { + // Paint terrain bits with rect. drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS_RECT; drag_modified.clear(); Dictionary painted_dict; @@ -2063,40 +2239,47 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t // Save the old terrain_set and terrains bits. Dictionary dict; dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); Array array; for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); } dict["terrain_peering_bits"] = array; drag_modified[cell] = dict; // Set the terrain bit. Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + tile_data->get_texture_origin(); + Vector<Vector2> polygon = tile_set->get_terrain_polygon(terrain_set); + if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { + tile_data->set_terrain(terrain); + } for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { + polygon = tile_set->get_terrain_peering_bit_polygon(terrain_set, bit); if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { - tile_data->set_peering_bit_terrain(bit, terrain); + tile_data->set_terrain_peering_bit(bit, terrain); } } } } drag_last_pos = mb->get_position(); } + accept_event(); } } } else { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET_RECT) { Rect2i rect; - rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); - rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position())); + rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos, true)); + rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position(), true)); rect = rect.abs(); - Set<TileMapCell> edited; + RBSet<TileMapCell> edited; for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); @@ -2111,20 +2294,24 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t } } undo_redo->create_action(TTR("Painting Terrain Set")); - for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { - Vector2i coords = E->get().get_atlas_coords(); - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->get().alternative_tile), tile_data->get_terrain_set()); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->get().alternative_tile), drag_painted_value); - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_data->is_valid_peering_bit_terrain(bit)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->get().alternative_tile), tile_data->get_peering_bit_terrain(bit)); + for (const TileMapCell &E : edited) { + Vector2i coords = E.get_atlas_coords(); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.alternative_tile), drag_painted_value); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.alternative_tile), tile_data->get_terrain_set()); + if (tile_data->get_terrain_set() >= 0) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.alternative_tile), tile_data->get_terrain()); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_data->is_valid_terrain_peering_bit(bit)) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.alternative_tile), tile_data->get_terrain_peering_bit(bit)); + } } } } undo_redo->commit_action(true); drag_type = DRAG_TYPE_NONE; + accept_event(); } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { undo_redo->create_action(TTR("Painting Terrain Set")); for (KeyValue<TileMapCell, Variant> &E : drag_modified) { @@ -2132,16 +2319,20 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t Vector2i coords = E.key.get_atlas_coords(); undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), drag_painted_value); undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), dict["terrain_set"]); - Array array = dict["terrain_peering_bits"]; - for (int i = 0; i < array.size(); i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); + if (int(dict["terrain_set"]) >= 0) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.key.alternative_tile), dict["terrain"]); + Array array = dict["terrain_peering_bits"]; + for (int i = 0; i < array.size(); i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_terrain_peering_bit(dict["terrain_set"], bit)) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); + } } } } undo_redo->commit_action(false); drag_type = DRAG_TYPE_NONE; + accept_event(); } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { Dictionary painted = Dictionary(drag_painted_value); int terrain_set = int(painted["terrain_set"]); @@ -2150,36 +2341,39 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t for (KeyValue<TileMapCell, Variant> &E : drag_modified) { Dictionary dict = E.value; Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.key.alternative_tile), terrain); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.key.alternative_tile), dict["terrain"]); Array array = dict["terrain_peering_bits"]; for (int i = 0; i < array.size(); i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), terrain); } - if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) { + if (tile_set->is_valid_terrain_peering_bit(dict["terrain_set"], bit)) { undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); } } } undo_redo->commit_action(false); drag_type = DRAG_TYPE_NONE; + accept_event(); } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS_RECT) { Dictionary painted = Dictionary(drag_painted_value); int terrain_set = int(painted["terrain_set"]); int terrain = int(painted["terrain"]); Rect2i rect; - rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); - rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position())); + rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos, true)); + rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position(), true)); rect = rect.abs(); - Set<TileMapCell> edited; + RBSet<TileMapCell> edited; for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); coords = p_tile_set_atlas_source->get_tile_at_coords(coords); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); if (tile_data->get_terrain_set() == terrain_set) { TileMapCell cell; cell.source_id = 0; @@ -2198,30 +2392,41 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t mouse_pos_rect_polygon.push_back(Vector2(drag_start_pos.x, mb->get_position().y)); undo_redo->create_action(TTR("Painting Terrain")); - for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { - Vector2i coords = E->get().get_atlas_coords(); - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + for (const TileMapCell &E : edited) { + Vector2i coords = E.get_atlas_coords(); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); + + Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); + Vector2i position = texture_region.get_center() + tile_data->get_texture_origin(); + + Vector<Vector2> polygon = tile_set->get_terrain_polygon(terrain_set); + for (int j = 0; j < polygon.size(); j++) { + polygon.write[j] += position; + } + if (!Geometry2D::intersect_polygons(polygon, mouse_pos_rect_polygon).is_empty()) { + // Draw terrain. + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.alternative_tile), terrain); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.alternative_tile), tile_data->get_terrain()); + } for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); - - Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { + polygon = tile_set->get_terrain_peering_bit_polygon(terrain_set, bit); for (int j = 0; j < polygon.size(); j++) { polygon.write[j] += position; } if (!Geometry2D::intersect_polygons(polygon, mouse_pos_rect_polygon).is_empty()) { // Draw bit. - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->get().alternative_tile), terrain); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->get().alternative_tile), tile_data->get_peering_bit_terrain(bit)); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.alternative_tile), terrain); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.alternative_tile), tile_data->get_terrain_peering_bit(bit)); } } } } undo_redo->commit_action(true); drag_type = DRAG_TYPE_NONE; + accept_event(); } } } @@ -2241,14 +2446,15 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi cell.source_id = 0; cell.set_atlas_coords(coords); cell.alternative_tile = alternative_tile; - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, alternative_tile); if (!drag_modified.has(cell)) { Dictionary dict; dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); Array array; for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); } dict["terrain_peering_bits"] = array; drag_modified[cell] = dict; @@ -2257,6 +2463,7 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi } drag_last_pos = mm->get_position(); + accept_event(); } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { Dictionary painted = Dictionary(drag_painted_value); int terrain_set = int(painted["terrain_set"]); @@ -2273,15 +2480,16 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi cell.alternative_tile = alternative_tile; // Save the old terrain_set and terrains bits. - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, alternative_tile); if (tile_data->get_terrain_set() == terrain_set) { if (!drag_modified.has(cell)) { Dictionary dict; dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); Array array; for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); } dict["terrain_peering_bits"] = array; drag_modified[cell] = dict; @@ -2289,50 +2497,64 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi // Set the terrains bits. Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); + Vector2i position = texture_region.get_center() + tile_data->get_texture_origin(); + + Vector<Vector2> polygon = tile_set->get_terrain_polygon(tile_data->get_terrain_set()); + if (Geometry2D::is_segment_intersecting_polygon(mm->get_position() - position, drag_last_pos - position, polygon)) { + tile_data->set_terrain(terrain); + } + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - if (tile_data->is_valid_peering_bit_terrain(bit)) { - Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(tile_data->get_terrain_set(), bit); + if (tile_data->is_valid_terrain_peering_bit(bit)) { + polygon = tile_set->get_terrain_peering_bit_polygon(tile_data->get_terrain_set(), bit); if (Geometry2D::is_segment_intersecting_polygon(mm->get_position() - position, drag_last_pos - position, polygon)) { - tile_data->set_peering_bit_terrain(bit, terrain); + tile_data->set_terrain_peering_bit(bit, terrain); } } } } } drag_last_pos = mm->get_position(); + accept_event(); } } Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MouseButton::LEFT) { + if (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT) { if (mb->is_pressed()) { - if (picker_button->is_pressed()) { + if (mb->get_button_index() == MouseButton::LEFT && picker_button->is_pressed()) { Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position()); Vector2i coords = Vector2i(tile.x, tile.y); int alternative_tile = tile.z; if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, alternative_tile); int terrain_set = tile_data->get_terrain_set(); Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); + Vector2i position = texture_region.get_center() + tile_data->get_texture_origin(); dummy_object->set("terrain_set", terrain_set); dummy_object->set("terrain", -1); + + Vector<Vector2> polygon = tile_set->get_terrain_polygon(terrain_set); + if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { + dummy_object->set("terrain", tile_data->get_terrain()); + } + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { + polygon = tile_set->get_terrain_peering_bit_polygon(terrain_set, bit); if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { - dummy_object->set("terrain", tile_data->get_peering_bit_terrain(bit)); + dummy_object->set("terrain", tile_data->get_terrain_peering_bit(bit)); } } } terrain_set_property_editor->update_property(); _update_terrain_selector(); picker_button->set_pressed(false); + accept_event(); } } else { int terrain_set = int(dummy_object->get("terrain_set")); @@ -2342,86 +2564,106 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi Vector2i coords = Vector2i(tile.x, tile.y); int alternative_tile = tile.z; - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, alternative_tile); - if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) { - drag_type = DRAG_TYPE_PAINT_TERRAIN_SET; - drag_modified.clear(); - drag_painted_value = int(dummy_object->get("terrain_set")); - if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileMapCell cell; - cell.source_id = 0; - cell.set_atlas_coords(coords); - cell.alternative_tile = alternative_tile; - Dictionary dict; - dict["terrain_set"] = tile_data->get_terrain_set(); - Array array; - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) { + // Paint terrain sets. + drag_type = DRAG_TYPE_PAINT_TERRAIN_SET; + drag_modified.clear(); + drag_painted_value = int(dummy_object->get("terrain_set")); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = alternative_tile; + Dictionary dict; + dict["terrain_set"] = tile_data->get_terrain_set(); + Array array; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); + } + dict["terrain_peering_bits"] = array; + drag_modified[cell] = dict; + tile_data->set_terrain_set(drag_painted_value); } - dict["terrain_peering_bits"] = array; - drag_modified[cell] = dict; - tile_data->set_terrain_set(drag_painted_value); - } - drag_last_pos = mb->get_position(); - } else if (tile_data && tile_data->get_terrain_set() == terrain_set) { - // Paint terrain bits. - drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS; - drag_modified.clear(); - Dictionary painted_dict; - painted_dict["terrain_set"] = terrain_set; - painted_dict["terrain"] = terrain; - drag_painted_value = painted_dict; - - if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileMapCell cell; - cell.source_id = 0; - cell.set_atlas_coords(coords); - cell.alternative_tile = alternative_tile; - - // Save the old terrain_set and terrains bits. - Dictionary dict; - dict["terrain_set"] = tile_data->get_terrain_set(); - Array array; - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + drag_last_pos = mb->get_position(); + accept_event(); + } else if (tile_data->get_terrain_set() == terrain_set) { + // Paint terrain bits. + if (mb->get_button_index() == MouseButton::RIGHT) { + terrain = -1; } - dict["terrain_peering_bits"] = array; - drag_modified[cell] = dict; + // Paint terrain bits. + drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS; + drag_modified.clear(); + Dictionary painted_dict; + painted_dict["terrain_set"] = terrain_set; + painted_dict["terrain"] = terrain; + drag_painted_value = painted_dict; - // Set the terrain bit. - Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); - Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); - if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { - tile_data->set_peering_bit_terrain(bit, terrain); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = alternative_tile; + + // Save the old terrain_set and terrains bits. + Dictionary dict; + dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); + Array array; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); + } + dict["terrain_peering_bits"] = array; + drag_modified[cell] = dict; + + // Set the terrain bit. + Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); + Vector2i position = texture_region.get_center() + tile_data->get_texture_origin(); + + Vector<Vector2> polygon = tile_set->get_terrain_polygon(terrain_set); + if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { + tile_data->set_terrain(terrain); + } + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { + polygon = tile_set->get_terrain_peering_bit_polygon(terrain_set, bit); + if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { + tile_data->set_terrain_peering_bit(bit, terrain); + } } } } + drag_last_pos = mb->get_position(); + accept_event(); } - drag_last_pos = mb->get_position(); } } } else { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { undo_redo->create_action(TTR("Painting Tiles Property")); for (KeyValue<TileMapCell, Variant> &E : drag_modified) { Dictionary dict = E.value; Vector2i coords = E.key.get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), dict["terrain_set"]); undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), drag_painted_value); - Array array = dict["terrain_peering_bits"]; - for (int i = 0; i < array.size(); i++) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), dict["terrain_set"]); + if (int(dict["terrain_set"]) >= 0) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.key.alternative_tile), dict["terrain"]); + Array array = dict["terrain_peering_bits"]; + for (int i = 0; i < array.size(); i++) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); + } } } undo_redo->commit_action(false); drag_type = DRAG_TYPE_NONE; + accept_event(); } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { Dictionary painted = Dictionary(drag_painted_value); int terrain_set = int(painted["terrain_set"]); @@ -2430,19 +2672,22 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi for (KeyValue<TileMapCell, Variant> &E : drag_modified) { Dictionary dict = E.value; Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.key.alternative_tile), terrain); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.key.alternative_tile), dict["terrain"]); Array array = dict["terrain_peering_bits"]; for (int i = 0; i < array.size(); i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), terrain); } - if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) { + if (tile_set->is_valid_terrain_peering_bit(dict["terrain_set"], bit)) { undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); } } } undo_redo->commit_action(false); drag_type = DRAG_TYPE_NONE; + accept_event(); } } } @@ -2459,22 +2704,19 @@ void TileDataTerrainsEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform void TileDataTerrainsEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); - break; - default: - break; + } break; } } TileDataTerrainsEditor::TileDataTerrainsEditor() { label = memnew(Label); - label->set_text("Painting:"); + label->set_text(TTR("Painting:")); + label->set_theme_type_variation("HeaderSmall"); add_child(label); // Toolbar - toolbar->add_child(memnew(VSeparator)); - picker_button = memnew(Button); picker_button->set_flat(true); picker_button->set_toggle_mode(true); @@ -2492,6 +2734,7 @@ TileDataTerrainsEditor::TileDataTerrainsEditor() { terrain_set_property_editor->set_object_and_property(dummy_object, "terrain_set"); terrain_set_property_editor->set_label("Terrain Set"); terrain_set_property_editor->connect("property_changed", callable_mp(this, &TileDataTerrainsEditor::_property_value_changed).unbind(1)); + terrain_set_property_editor->set_tooltip_text(terrain_set_property_editor->get_edited_property()); add_child(terrain_set_property_editor); terrain_property_editor = memnew(EditorPropertyEnum); @@ -2502,53 +2745,54 @@ TileDataTerrainsEditor::TileDataTerrainsEditor() { } TileDataTerrainsEditor::~TileDataTerrainsEditor() { - toolbar->queue_delete(); + toolbar->queue_free(); memdelete(dummy_object); } Variant TileDataNavigationEditor::_get_painted_value() { - Ref<NavigationPolygon> navigation_polygon; - navigation_polygon.instantiate(); + Ref<NavigationPolygon> nav_polygon; + nav_polygon.instantiate(); for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { Vector<Vector2> polygon = polygon_editor->get_polygon(i); - navigation_polygon->add_outline(polygon); + nav_polygon->add_outline(polygon); } - navigation_polygon->make_polygons_from_outlines(); - return navigation_polygon; + nav_polygon->make_polygons_from_outlines(); + return nav_polygon; } void TileDataNavigationEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); - Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer); + Ref<NavigationPolygon> nav_polygon = tile_data->get_navigation_polygon(navigation_layer); polygon_editor->clear_polygons(); - if (navigation_polygon.is_valid()) { - for (int i = 0; i < navigation_polygon->get_outline_count(); i++) { - polygon_editor->add_polygon(navigation_polygon->get_outline(i)); + if (nav_polygon.is_valid()) { + for (int i = 0; i < nav_polygon->get_outline_count(); i++) { + polygon_editor->add_polygon(nav_polygon->get_outline(i)); } } - polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); + polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); } void TileDataNavigationEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); - Ref<NavigationPolygon> navigation_polygon = p_value; - tile_data->set_navigation_polygon(navigation_layer, navigation_polygon); + Ref<NavigationPolygon> nav_polygon = p_value; + tile_data->set_navigation_polygon(navigation_layer, nav_polygon); - polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); + polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); } Variant TileDataNavigationEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND_V(!tile_data, Variant()); return tile_data->get_navigation_polygon(navigation_layer); } -void TileDataNavigationEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { +void TileDataNavigationEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { Vector2i coords = E.key.get_atlas_coords(); undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E.key.alternative_tile, navigation_layer), E.value); @@ -2562,11 +2806,11 @@ void TileDataNavigationEditor::_tile_set_changed() { void TileDataNavigationEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: - polygon_editor->set_polygons_color(get_tree()->get_debug_navigation_color()); - break; - default: - break; + case NOTIFICATION_ENTER_TREE: { +#ifdef DEBUG_ENABLED + polygon_editor->set_polygons_color(NavigationServer3D::get_singleton()->get_debug_navigation_geometry_face_color()); +#endif // DEBUG_ENABLED + } break; } } @@ -2583,25 +2827,28 @@ void TileDataNavigationEditor::draw_over_tile(CanvasItem *p_canvas_item, Transfo // Draw all shapes. RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); - Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer); - if (navigation_polygon.is_valid()) { - Vector<Vector2> verts = navigation_polygon->get_vertices(); + Ref<NavigationPolygon> nav_polygon = tile_data->get_navigation_polygon(navigation_layer); + if (nav_polygon.is_valid()) { + Vector<Vector2> verts = nav_polygon->get_vertices(); if (verts.size() < 3) { return; } - Color color = p_canvas_item->get_tree()->get_debug_navigation_color(); + Color color = Color(0.5, 1.0, 1.0, 1.0); +#ifdef DEBUG_ENABLED + color = NavigationServer3D::get_singleton()->get_debug_navigation_geometry_face_color(); +#endif // DEBUG_ENABLED if (p_selected) { - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); selection_color.a = 0.7; color = selection_color; } RandomPCG rand; - for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) { + for (int i = 0; i < nav_polygon->get_polygon_count(); i++) { // An array of vertices for this polygon. - Vector<int> polygon = navigation_polygon->get_polygon(i); + Vector<int> polygon = nav_polygon->get_polygon(i); Vector<Vector2> vertices; vertices.resize(polygon.size()); for (int j = 0; j < polygon.size(); j++) { diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h index b45eb9530b..1ebf30aecd 100644 --- a/editor/plugins/tiles/tile_data_editors.h +++ b/editor/plugins/tiles/tile_data_editors.h @@ -1,46 +1,47 @@ -/*************************************************************************/ -/* tile_data_editors.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_data_editors.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 TILE_DATA_EDITORS_H #define TILE_DATA_EDITORS_H #include "tile_atlas_view.h" -#include "editor/editor_node.h" #include "editor/editor_properties.h" - #include "scene/2d/tile_map.h" #include "scene/gui/box_container.h" #include "scene/gui/control.h" #include "scene/gui/label.h" +class MenuButton; +class EditorUndoRedoManager; + class TileDataEditor : public VBoxContainer { GDCLASS(TileDataEditor, VBoxContainer); @@ -73,7 +74,7 @@ public: class DummyObject : public Object { GDCLASS(DummyObject, Object) private: - Map<String, Variant> properties; + HashMap<String, Variant> properties; protected: bool _set(const StringName &p_name, const Variant &p_value); @@ -95,7 +96,6 @@ private: bool multiple_polygon_mode = false; bool use_undo_redo = true; - UndoRedo *editor_undo_redo = EditorNode::get_undo_redo(); // UI int hovered_polygon_index = -1; @@ -110,33 +110,33 @@ private: DRAG_TYPE_PAN, }; DragType drag_type = DRAG_TYPE_NONE; - int drag_polygon_index; - int drag_point_index; + int drag_polygon_index = 0; + int drag_point_index = 0; Vector2 drag_last_pos; PackedVector2Array drag_old_polygon; - HBoxContainer *toolbar; + HBoxContainer *toolbar = nullptr; Ref<ButtonGroup> tools_button_group; - Button *button_create; - Button *button_edit; - Button *button_delete; - Button *button_pixel_snap; - MenuButton *button_advanced_menu; + Button *button_create = nullptr; + Button *button_edit = nullptr; + Button *button_delete = nullptr; + Button *button_pixel_snap = nullptr; + MenuButton *button_advanced_menu = nullptr; Vector<Point2> in_creation_polygon; - Panel *panel; - Control *base_control; - EditorZoomWidget *editor_zoom_widget; - Button *button_center_view; + Panel *panel = nullptr; + Control *base_control = nullptr; + EditorZoomWidget *editor_zoom_widget = nullptr; + Button *button_center_view = nullptr; Vector2 panning; Ref<Texture2D> background_texture; Rect2 background_region; Vector2 background_offset; - bool background_h_flip; - bool background_v_flip; - bool background_transpose; + bool background_h_flip = false; + bool background_v_flip = false; + bool background_transpose = false; Color background_modulate; Color polygon_color = Color(1.0, 0.0, 0.0); @@ -190,12 +190,12 @@ class TileDataDefaultEditor : public TileDataEditor { private: // Toolbar HBoxContainer *toolbar = memnew(HBoxContainer); - Button *picker_button; + Button *picker_button = nullptr; // UI Ref<Texture2D> tile_bool_checked; Ref<Texture2D> tile_bool_unchecked; - Label *label; + Label *label = nullptr; EditorProperty *property_editor = nullptr; @@ -208,7 +208,7 @@ private: DragType drag_type = DRAG_TYPE_NONE; Vector2 drag_start_pos; Vector2 drag_last_pos; - Map<TileMapCell, Variant> drag_modified; + HashMap<TileMapCell, Variant, TileMapCell> drag_modified; Variant drag_painted_value; void _property_value_changed(StringName p_property, Variant p_value, StringName p_field); @@ -216,17 +216,16 @@ private: protected: DummyObject *dummy_object = memnew(DummyObject); - UndoRedo *undo_redo = EditorNode::get_undo_redo(); - StringName type; String property; + Variant::Type property_type; void _notification(int p_what); virtual Variant _get_painted_value(); virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile); virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value); virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile); - virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value); + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value); public: virtual Control *get_toolbar() override { return toolbar; }; @@ -237,13 +236,14 @@ public: virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; void setup_property_editor(Variant::Type p_type, String p_property, String p_label = "", Variant p_default_value = Variant()); + Variant::Type get_property_type(); TileDataDefaultEditor(); ~TileDataDefaultEditor(); }; -class TileDataTextureOffsetEditor : public TileDataDefaultEditor { - GDCLASS(TileDataTextureOffsetEditor, TileDataDefaultEditor); +class TileDataTextureOriginEditor : public TileDataDefaultEditor { + GDCLASS(TileDataTextureOriginEditor, TileDataDefaultEditor); public: virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; @@ -270,7 +270,7 @@ private: int occlusion_layer = -1; // UI - GenericTilePolygonEditor *polygon_editor; + GenericTilePolygonEditor *polygon_editor = nullptr; void _polygon_changed(PackedVector2Array p_polygon); @@ -278,11 +278,9 @@ private: virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; - virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override; + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) override; protected: - UndoRedo *undo_redo = EditorNode::get_undo_redo(); - virtual void _tile_set_changed() override; void _notification(int p_what); @@ -301,22 +299,21 @@ class TileDataCollisionEditor : public TileDataDefaultEditor { int physics_layer = -1; // UI - GenericTilePolygonEditor *polygon_editor; + GenericTilePolygonEditor *polygon_editor = nullptr; DummyObject *dummy_object = memnew(DummyObject); - Map<StringName, EditorProperty *> property_editors; + HashMap<StringName, EditorProperty *> property_editors; void _property_value_changed(StringName p_property, Variant p_value, StringName p_field); + void _property_selected(StringName p_path, int p_focusable); void _polygons_changed(); virtual Variant _get_painted_value() override; virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; - virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override; + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) override; protected: - UndoRedo *undo_redo = EditorNode::get_undo_redo(); - virtual void _tile_set_changed() override; void _notification(int p_what); @@ -336,7 +333,7 @@ class TileDataTerrainsEditor : public TileDataEditor { private: // Toolbar HBoxContainer *toolbar = memnew(HBoxContainer); - Button *picker_button; + Button *picker_button = nullptr; // Painting state. enum DragType { @@ -349,11 +346,11 @@ private: DragType drag_type = DRAG_TYPE_NONE; Vector2 drag_start_pos; Vector2 drag_last_pos; - Map<TileMapCell, Variant> drag_modified; + HashMap<TileMapCell, Variant, TileMapCell> drag_modified; Variant drag_painted_value; // UI - Label *label; + Label *label = nullptr; DummyObject *dummy_object = memnew(DummyObject); EditorPropertyEnum *terrain_set_property_editor = nullptr; EditorPropertyEnum *terrain_property_editor = nullptr; @@ -367,8 +364,6 @@ protected: void _notification(int p_what); - UndoRedo *undo_redo = EditorNode::get_undo_redo(); - public: virtual Control *get_toolbar() override { return toolbar; }; virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override; @@ -389,7 +384,7 @@ private: PackedVector2Array navigation_polygon; // UI - GenericTilePolygonEditor *polygon_editor; + GenericTilePolygonEditor *polygon_editor = nullptr; void _polygon_changed(PackedVector2Array p_polygon); @@ -397,11 +392,9 @@ private: virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; - virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override; + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) override; protected: - UndoRedo *undo_redo = EditorNode::get_undo_redo(); - virtual void _tile_set_changed() override; void _notification(int p_what); diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index aa92920722..4d9e2db682 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -1,39 +1,42 @@ -/*************************************************************************/ -/* tile_map_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_map_editor.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 "tile_map_editor.h" #include "tiles_editor_plugin.h" +#include "editor/editor_node.h" #include "editor/editor_resource_preview.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "scene/2d/camera_2d.h" @@ -52,7 +55,7 @@ void TileMapEditorTilesPlugin::tile_set_changed() { } void TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled(bool p_pressed) { - scatter_spinbox->set_editable(p_pressed); + scatter_controls_container->set_visible(p_pressed); } void TileMapEditorTilesPlugin::_on_scattering_spinbox_changed(double p_value) { @@ -75,7 +78,7 @@ void TileMapEditorTilesPlugin::_update_toolbar() { picker_button->show(); erase_button->show(); tools_settings_vsep_2->show(); - random_tile_checkbox->show(); + random_tile_toggle->show(); scatter_label->show(); scatter_spinbox->show(); } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { @@ -83,7 +86,7 @@ void TileMapEditorTilesPlugin::_update_toolbar() { picker_button->show(); erase_button->show(); tools_settings_vsep_2->show(); - random_tile_checkbox->show(); + random_tile_toggle->show(); scatter_label->show(); scatter_spinbox->show(); } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) { @@ -91,7 +94,7 @@ void TileMapEditorTilesPlugin::_update_toolbar() { picker_button->show(); erase_button->show(); tools_settings_vsep_2->show(); - random_tile_checkbox->show(); + random_tile_toggle->show(); scatter_label->show(); scatter_spinbox->show(); } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) { @@ -100,7 +103,7 @@ void TileMapEditorTilesPlugin::_update_toolbar() { erase_button->show(); tools_settings_vsep_2->show(); bucket_contiguous_checkbox->show(); - random_tile_checkbox->show(); + random_tile_toggle->show(); scatter_label->show(); scatter_spinbox->show(); } @@ -124,6 +127,10 @@ void TileMapEditorTilesPlugin::_tab_changed() { void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { // Update the sources. int old_current = sources_list->get_current(); + int old_source = -1; + if (old_current > -1) { + old_source = sources_list->get_item_metadata(old_current); + } sources_list->clear(); TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); @@ -136,9 +143,12 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { return; } - for (int i = 0; i < tile_set->get_source_count(); i++) { - int source_id = tile_set->get_source_id(i); + if (!tile_set->has_source(old_source)) { + old_source = -1; + } + List<int> source_ids = TilesEditorPlugin::get_singleton()->get_sorted_sources(tile_set); + for (const int &source_id : source_ids) { TileSetSource *source = *tile_set->get_source(source_id); Ref<Texture2D> texture; @@ -146,7 +156,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { // Common to all type of sources. if (!source->get_name().is_empty()) { - item_text = vformat(TTR("%s (id:%d)"), source->get_name(), source_id); + item_text = vformat(TTR("%s (ID: %d)"), source->get_name(), source_id); } // Atlas source. @@ -155,9 +165,9 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { texture = atlas_source->get_texture(); if (item_text.is_empty()) { if (texture.is_valid()) { - item_text = vformat("%s (ID: %d)", texture->get_path().get_file(), source_id); + item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id); } else { - item_text = vformat("No Texture Atlas Source (ID: %d)", source_id); + item_text = vformat(TTR("No Texture Atlas Source (ID: %d)"), source_id); } } } @@ -180,20 +190,25 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { } sources_list->add_item(item_text, texture); - sources_list->set_item_metadata(i, source_id); + sources_list->set_item_metadata(-1, source_id); } if (sources_list->get_item_count() > 0) { - if (old_current > 0) { - // Keep the current selected item if needed. - sources_list->set_current(CLAMP(old_current, 0, sources_list->get_item_count() - 1)); + if (old_source >= 0) { + for (int i = 0; i < sources_list->get_item_count(); i++) { + if ((int)sources_list->get_item_metadata(i) == old_source) { + sources_list->set_current(i); + sources_list->ensure_current_is_visible(); + break; + } + } } else { sources_list->set_current(0); } sources_list->emit_signal(SNAME("item_selected"), sources_list->get_current()); } - // Synchronize + // Synchronize the lists. TilesEditorPlugin::get_singleton()->set_sources_lists_current(sources_list->get_current()); } @@ -251,13 +266,14 @@ void TileMapEditorTilesPlugin::_patterns_item_list_gui_input(const Ref<InputEven } Ref<TileSet> tile_set = tile_map->get_tileset(); - if (!tile_set.is_valid()) { + if (!tile_set.is_valid() || EditorNode::get_singleton()->is_resource_read_only(tile_set)) { return; } if (ED_IS_SHORTCUT("tiles_editor/paste", p_event) && p_event->is_pressed() && !p_event->is_echo()) { select_last_pattern = true; int new_pattern_index = tile_set->get_patterns_count(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add TileSet pattern")); undo_redo->add_do_method(*tile_set, "add_pattern", tile_map_clipboard, new_pattern_index); undo_redo->add_undo_method(*tile_set, "remove_pattern", new_pattern_index); @@ -267,6 +283,7 @@ void TileMapEditorTilesPlugin::_patterns_item_list_gui_input(const Ref<InputEven if (ED_IS_SHORTCUT("tiles_editor/delete", p_event) && p_event->is_pressed() && !p_event->is_echo()) { Vector<int> selected = patterns_item_list->get_selected_items(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove TileSet patterns")); for (int i = 0; i < selected.size(); i++) { int pattern_index = selected[i]; @@ -337,7 +354,7 @@ void TileMapEditorTilesPlugin::_update_atlas_view() { tile_atlas_view->set_atlas_source(*tile_map->get_tileset(), atlas_source, source_id); TilesEditorPlugin::get_singleton()->synchronize_atlas_view(tile_atlas_view); - tile_atlas_control->update(); + tile_atlas_control->queue_redraw(); } void TileMapEditorTilesPlugin::_update_scenes_collection_view() { @@ -382,7 +399,7 @@ void TileMapEditorTilesPlugin::_update_scenes_collection_view() { } // Icon size update. - int int_size = int(EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size")) * EDSCALE; + int int_size = int(EDITOR_GET("filesystem/file_dialog/thumbnail_size")) * EDSCALE; scene_tiles_list->set_fixed_icon_size(Vector2(int_size, int_size)); } @@ -430,7 +447,11 @@ void TileMapEditorTilesPlugin::_scenes_list_multi_selected(int p_index, bool p_s _update_selection_pattern_from_tileset_tiles_selection(); } -void TileMapEditorTilesPlugin::_scenes_list_nothing_selected() { +void TileMapEditorTilesPlugin::_scenes_list_lmb_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index) { + if (p_mouse_button_index != MouseButton::LEFT) { + return; + } + scene_tiles_list->deselect_all(); tile_set_selection.clear(); tile_map_selection.clear(); @@ -439,16 +460,19 @@ void TileMapEditorTilesPlugin::_scenes_list_nothing_selected() { } void TileMapEditorTilesPlugin::_update_theme() { + source_sort_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Sort"), SNAME("EditorIcons"))); select_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); paint_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); - line_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("CurveLinear"), SNAME("EditorIcons"))); + line_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Line"), SNAME("EditorIcons"))); rect_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Rectangle"), SNAME("EditorIcons"))); bucket_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Bucket"), SNAME("EditorIcons"))); picker_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); erase_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons"))); + random_tile_toggle->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("RandomNumberGenerator"), SNAME("EditorIcons"))); missing_atlas_texture_icon = tiles_bottom_panel->get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons")); + _update_tile_set_sources_list(); } bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { @@ -482,8 +506,8 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (!tile_map_selection.is_empty()) { tile_map_clipboard.instantiate(); TypedArray<Vector2i> coords_array; - for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - coords_array.push_back(E->get()); + for (const Vector2i &E : tile_map_selection) { + coords_array.push_back(E); } tile_map_clipboard = tile_map->get_pattern(tile_map_layer, coords_array); } @@ -491,10 +515,11 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (ED_IS_SHORTCUT("tiles_editor/cut", p_event)) { // Delete selected tiles. if (!tile_map_selection.is_empty()) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Delete tiles")); - for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->get(), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->get(), tile_map->get_cell_source_id(tile_map_layer, E->get()), tile_map->get_cell_atlas_coords(tile_map_layer, E->get()), tile_map->get_cell_alternative_tile(tile_map_layer, E->get())); + for (const Vector2i &E : tile_map_selection) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E, tile_map->get_cell_source_id(tile_map_layer, E), tile_map->get_cell_atlas_coords(tile_map_layer, E), tile_map->get_cell_alternative_tile(tile_map_layer, E)); } undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); tile_map_selection.clear(); @@ -522,10 +547,11 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (ED_IS_SHORTCUT("tiles_editor/delete", p_event)) { // Delete selected tiles. if (!tile_map_selection.is_empty()) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Delete tiles")); - for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->get(), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->get(), tile_map->get_cell_source_id(tile_map_layer, E->get()), tile_map->get_cell_atlas_coords(tile_map_layer, E->get()), tile_map->get_cell_alternative_tile(tile_map_layer, E->get())); + for (const Vector2i &E : tile_map_selection) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E, tile_map->get_cell_source_id(tile_map_layer, E), tile_map->get_cell_atlas_coords(tile_map_layer, E), tile_map->get_cell_alternative_tile(tile_map_layer, E)); } undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); tile_map_selection.clear(); @@ -543,33 +569,33 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p switch (drag_type) { case DRAG_TYPE_PAINT: { - Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_last_mouse_pos, mpos, drag_erasing); + HashMap<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_last_mouse_pos, mpos, drag_erasing); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { - continue; - } Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { + continue; + } + tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } - tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } _fix_invalid_tiles_in_tile_map_selection(); } break; case DRAG_TYPE_BUCKET: { - Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); + Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->local_to_map(drag_last_mouse_pos), tile_map->local_to_map(mpos)); for (int i = 0; i < line.size(); i++) { if (!drag_modified.has(line[i])) { - Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_contiguous_checkbox->is_pressed(), drag_erasing); + HashMap<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_contiguous_checkbox->is_pressed(), drag_erasing); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { - continue; - } Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { + continue; + } + tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } - tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } } } @@ -604,13 +630,13 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } } else if (tool_buttons_group->get_pressed_button() == select_tool_button) { drag_start_mouse_pos = mpos; - if (tile_map_selection.has(tile_map->world_to_map(drag_start_mouse_pos)) && !mb->is_shift_pressed()) { + if (tile_map_selection.has(tile_map->local_to_map(drag_start_mouse_pos)) && !mb->is_shift_pressed()) { // Move the selection _update_selection_pattern_from_tilemap_selection(); // Make sure the pattern is up to date before moving. drag_type = DRAG_TYPE_MOVE; drag_modified.clear(); - for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - Vector2i coords = E->get(); + for (const Vector2i &E : tile_map_selection) { + Vector2i coords = E; drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); tile_map->set_cell(tile_map_layer, coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); } @@ -620,16 +646,16 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } } else { // Check if we are picking a tile. - if (picker_button->is_pressed() || (Input::get_singleton()->is_key_pressed(Key::CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { + if (picker_button->is_pressed() || (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { drag_type = DRAG_TYPE_PICK; drag_start_mouse_pos = mpos; } else { // Paint otherwise. - if (tool_buttons_group->get_pressed_button() == paint_tool_button && !Input::get_singleton()->is_key_pressed(Key::CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT)) { + if (tool_buttons_group->get_pressed_button() == paint_tool_button && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT)) { drag_type = DRAG_TYPE_PAINT; drag_start_mouse_pos = mpos; drag_modified.clear(); - Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, mpos, mpos, drag_erasing); + HashMap<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, mpos, mpos, drag_erasing); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { continue; @@ -641,11 +667,11 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } _fix_invalid_tiles_in_tile_map_selection(); - } else if (tool_buttons_group->get_pressed_button() == line_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(Key::SHIFT) && !Input::get_singleton()->is_key_pressed(Key::CTRL))) { + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(Key::SHIFT) && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL))) { drag_type = DRAG_TYPE_LINE; drag_start_mouse_pos = mpos; drag_modified.clear(); - } else if (tool_buttons_group->get_pressed_button() == rect_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(Key::SHIFT) && Input::get_singleton()->is_key_pressed(Key::CTRL))) { + } else if (tool_buttons_group->get_pressed_button() == rect_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(Key::SHIFT) && Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL))) { drag_type = DRAG_TYPE_RECT; drag_start_mouse_pos = mpos; drag_modified.clear(); @@ -653,19 +679,19 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p drag_type = DRAG_TYPE_BUCKET; drag_start_mouse_pos = mpos; drag_modified.clear(); - Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); + Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->local_to_map(drag_last_mouse_pos), tile_map->local_to_map(mpos)); for (int i = 0; i < line.size(); i++) { if (!drag_modified.has(line[i])) { - Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_contiguous_checkbox->is_pressed(), drag_erasing); + HashMap<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_contiguous_checkbox->is_pressed(), drag_erasing); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { - continue; - } Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { + continue; + } + tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } - tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } } } @@ -716,10 +742,10 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over // Draw the selection. if ((tiles_bottom_panel->is_visible_in_tree() || patterns_bottom_panel->is_visible_in_tree()) && tool_buttons_group->get_pressed_button() == select_tool_button) { // In select mode, we only draw the current selection if we are modifying it (pressing control or shift). - if (drag_type == DRAG_TYPE_MOVE || (drag_type == DRAG_TYPE_SELECT && !Input::get_singleton()->is_key_pressed(Key::CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { + if (drag_type == DRAG_TYPE_MOVE || (drag_type == DRAG_TYPE_SELECT && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { // Do nothing } else { - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); tile_map->draw_cells_outline(p_overlay, tile_map_selection, selection_color, xform); } @@ -727,19 +753,19 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over // Handle the preview of the tiles to be placed. if ((tiles_bottom_panel->is_visible_in_tree() || patterns_bottom_panel->is_visible_in_tree()) && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered. - Map<Vector2i, TileMapCell> preview; + HashMap<Vector2i, TileMapCell> preview; Rect2i drawn_grid_rect; if (drag_type == DRAG_TYPE_PICK) { // Draw the area being picked. - Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos) - tile_map->world_to_map(drag_start_mouse_pos)).abs(); + Rect2i rect = Rect2i(tile_map->local_to_map(drag_start_mouse_pos), tile_map->local_to_map(drag_last_mouse_pos) - tile_map->local_to_map(drag_start_mouse_pos)).abs(); rect.size += Vector2i(1, 1); for (int x = rect.position.x; x < rect.get_end().x; x++) { for (int y = rect.position.y; y < rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) { Transform2D tile_xform; - tile_xform.set_origin(tile_map->map_to_world(coords)); + tile_xform.set_origin(tile_map->map_to_local(coords)); tile_xform.set_scale(tile_shape_size); tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0), false); } @@ -747,9 +773,9 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over } } else if (drag_type == DRAG_TYPE_SELECT) { // Draw the area being selected. - Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos) - tile_map->world_to_map(drag_start_mouse_pos)).abs(); + Rect2i rect = Rect2i(tile_map->local_to_map(drag_start_mouse_pos), tile_map->local_to_map(drag_last_mouse_pos) - tile_map->local_to_map(drag_start_mouse_pos)).abs(); rect.size += Vector2i(1, 1); - Set<Vector2i> to_draw; + RBSet<Vector2i> to_draw; for (int x = rect.position.x; x < rect.get_end().x; x++) { for (int y = rect.position.y; y < rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); @@ -766,11 +792,11 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over if (!tile_map_selection.is_empty()) { top_left = tile_map_selection.front()->get(); } - for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - top_left = top_left.min(E->get()); + for (const Vector2i &E : tile_map_selection) { + top_left = top_left.min(E); } - Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left); - offset = tile_map->world_to_map(drag_last_mouse_pos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset); + Vector2i offset = drag_start_mouse_pos - tile_map->map_to_local(top_left); + offset = tile_map->local_to_map(drag_last_mouse_pos - offset) - tile_map->local_to_map(drag_start_mouse_pos - offset); TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells(); for (int i = 0; i < selection_used_cells.size(); i++) { @@ -783,10 +809,10 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over Vector2 mouse_offset = (Vector2(tile_map_clipboard->get_size()) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size(); TypedArray<Vector2i> clipboard_used_cells = tile_map_clipboard->get_used_cells(); for (int i = 0; i < clipboard_used_cells.size(); i++) { - Vector2i coords = tile_map->map_pattern(tile_map->world_to_map(drag_last_mouse_pos - mouse_offset), clipboard_used_cells[i], tile_map_clipboard); + Vector2i coords = tile_map->map_pattern(tile_map->local_to_map(drag_last_mouse_pos - mouse_offset), clipboard_used_cells[i], tile_map_clipboard); preview[coords] = TileMapCell(tile_map_clipboard->get_cell_source_id(clipboard_used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(clipboard_used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(clipboard_used_cells[i])); } - } else if (!picker_button->is_pressed() && !(drag_type == DRAG_TYPE_NONE && Input::get_singleton()->is_key_pressed(Key::CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { + } else if (!picker_button->is_pressed() && !(drag_type == DRAG_TYPE_NONE && Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { bool expand_grid = false; if (tool_buttons_group->get_pressed_button() == paint_tool_button && drag_type == DRAG_TYPE_NONE) { // Preview for a single pattern. @@ -804,16 +830,16 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over } } else if (drag_type == DRAG_TYPE_RECT) { // Preview for a rect pattern. - preview = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos), drag_erasing); + preview = _draw_rect(tile_map->local_to_map(drag_start_mouse_pos), tile_map->local_to_map(drag_last_mouse_pos), drag_erasing); expand_grid = true; } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button && drag_type == DRAG_TYPE_NONE) { // Preview for a fill pattern. - preview = _draw_bucket_fill(tile_map->world_to_map(drag_last_mouse_pos), bucket_contiguous_checkbox->is_pressed(), erase_button->is_pressed()); + preview = _draw_bucket_fill(tile_map->local_to_map(drag_last_mouse_pos), bucket_contiguous_checkbox->is_pressed(), erase_button->is_pressed()); } // Expand the grid if needed if (expand_grid && !preview.is_empty()) { - drawn_grid_rect = Rect2i(preview.front()->key(), Vector2i(1, 1)); + drawn_grid_rect = Rect2i(preview.begin()->key, Vector2i(1, 1)); for (const KeyValue<Vector2i, TileMapCell> &E : preview) { drawn_grid_rect.expand_to(E.key); } @@ -824,9 +850,9 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over const int fading = 5; // Draw the lines of the grid behind the preview. - bool display_grid = EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid"); + bool display_grid = EDITOR_GET("editors/tiles_editor/display_grid"); if (display_grid) { - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); if (drawn_grid_rect.size.x > 0 && drawn_grid_rect.size.y > 0) { drawn_grid_rect = drawn_grid_rect.grow(fading); for (int x = drawn_grid_rect.position.x; x < (drawn_grid_rect.position.x + drawn_grid_rect.size.x); x++) { @@ -835,13 +861,13 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over // Fade out the border of the grid. float left_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.x), 0.0f, 1.0f); - float right_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.x, (float)(drawn_grid_rect.size.x - fading), (float)pos_in_rect.x), 0.0f, 1.0f); + float right_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.x, (float)(drawn_grid_rect.size.x - fading), (float)(pos_in_rect.x + 1)), 0.0f, 1.0f); float top_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.y), 0.0f, 1.0f); - float bottom_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.y, (float)(drawn_grid_rect.size.y - fading), (float)pos_in_rect.y), 0.0f, 1.0f); + float bottom_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.y, (float)(drawn_grid_rect.size.y - fading), (float)(pos_in_rect.y + 1)), 0.0f, 1.0f); float opacity = CLAMP(MIN(left_opacity, MIN(right_opacity, MIN(top_opacity, bottom_opacity))) + 0.1, 0.0f, 1.0f); Transform2D tile_xform; - tile_xform.set_origin(tile_map->map_to_world(Vector2(x, y))); + tile_xform.set_origin(tile_map->map_to_local(Vector2(x, y))); tile_xform.set_scale(tile_shape_size); Color color = grid_color; color.a = color.a * opacity; @@ -854,9 +880,9 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over // Draw the preview. for (const KeyValue<Vector2i, TileMapCell> &E : preview) { Transform2D tile_xform; - tile_xform.set_origin(tile_map->map_to_world(E.key)); + tile_xform.set_origin(tile_map->map_to_local(E.key)); tile_xform.set_scale(tile_set->get_tile_size()); - if (!(drag_erasing || erase_button->is_pressed()) && random_tile_checkbox->is_pressed()) { + if (!(drag_erasing || erase_button->is_pressed()) && random_tile_toggle->is_pressed()) { tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true); } else { if (tile_set->has_source(E.value.source_id)) { @@ -864,11 +890,14 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { // Get tile data. - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E.value.get_atlas_coords(), E.value.alternative_tile)); + TileData *tile_data = atlas_source->get_tile_data(E.value.get_atlas_coords(), E.value.alternative_tile); + if (!tile_data) { + continue; + } // Compute the offset Rect2i source_rect = atlas_source->get_tile_texture_region(E.value.get_atlas_coords()); - Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(E.value.get_atlas_coords(), E.value.alternative_tile); + Vector2i tile_offset = tile_data->get_texture_origin(); // Compute the destination rectangle in the CanvasItem. Rect2 dest_rect; @@ -876,9 +905,9 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over bool transpose = tile_data->get_transpose(); if (transpose) { - dest_rect.position = (tile_map->map_to_world(E.key) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); + dest_rect.position = (tile_map->map_to_local(E.key) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); } else { - dest_rect.position = (tile_map->map_to_world(E.key) - dest_rect.size / 2 - tile_offset); + dest_rect.position = (tile_map->map_to_local(E.key) - dest_rect.size / 2 - tile_offset); } dest_rect = xform.xform(dest_rect); @@ -893,8 +922,9 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over // Get the tile modulation. Color modulate = tile_data->get_modulate(); - Color self_modulate = tile_map->get_self_modulate(); + Color self_modulate = tile_map->get_modulate_in_tree() * tile_map->get_self_modulate(); modulate *= self_modulate; + modulate *= tile_map->get_layer_modulate(tile_map_layer); // Draw the tile. p_overlay->draw_texture_rect_region(atlas_source->get_texture(), dest_rect, source_rect, modulate * Color(1.0, 1.0, 1.0, 0.5), transpose, tile_set->is_uv_clipping()); @@ -936,7 +966,7 @@ TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(Ref<TileMapPattern> p_pa TileSetSource *source = *tile_set->get_source(source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile)); + TileData *tile_data = atlas_source->get_tile_data(atlas_coords, alternative_tile); ERR_FAIL_COND_V(!tile_data, TileMapCell()); sum += tile_data->get_probability(); } else { @@ -955,7 +985,7 @@ TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(Ref<TileMapPattern> p_pa TileSetSource *source = *tile_set->get_source(source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { - current += Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile))->get_probability(); + current += atlas_source->get_tile_data(atlas_coords, alternative_tile)->get_probability(); } else { current += 1.0; } @@ -967,15 +997,15 @@ TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(Ref<TileMapPattern> p_pa return TileMapCell(); } -Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos, bool p_erase) { +HashMap<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos, bool p_erase) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } // Get or create the pattern. @@ -984,12 +1014,12 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_ erase_pattern->set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); Ref<TileMapPattern> pattern = p_erase ? erase_pattern : selection_pattern; - Map<Vector2i, TileMapCell> output; + HashMap<Vector2i, TileMapCell> output; if (!pattern->is_empty()) { // Paint the tiles on the tile map. - if (!p_erase && random_tile_checkbox->is_pressed()) { + if (!p_erase && random_tile_toggle->is_pressed()) { // Paint a random tile. - Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(p_from_mouse_pos), tile_map->world_to_map(p_to_mouse_pos)); + Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->local_to_map(p_from_mouse_pos), tile_map->local_to_map(p_to_mouse_pos)); for (int i = 0; i < line.size(); i++) { output.insert(line[i], _pick_random_tile(pattern)); } @@ -997,9 +1027,9 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_ // Paint the pattern. // If we paint several tiles, we virtually move the mouse as if it was in the center of the "brush" Vector2 mouse_offset = (Vector2(pattern->get_size()) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size(); - Vector2i last_hovered_cell = tile_map->world_to_map(p_from_mouse_pos - mouse_offset); - Vector2i new_hovered_cell = tile_map->world_to_map(p_to_mouse_pos - mouse_offset); - Vector2i drag_start_cell = tile_map->world_to_map(p_start_drag_mouse_pos - mouse_offset); + Vector2i last_hovered_cell = tile_map->local_to_map(p_from_mouse_pos - mouse_offset); + Vector2i new_hovered_cell = tile_map->local_to_map(p_to_mouse_pos - mouse_offset); + Vector2i drag_start_cell = tile_map->local_to_map(p_start_drag_mouse_pos - mouse_offset); TypedArray<Vector2i> used_cells = pattern->get_used_cells(); Vector2i offset = Vector2i(Math::posmod(drag_start_cell.x, pattern->get_size().x), Math::posmod(drag_start_cell.y, pattern->get_size().y)); // Note: no posmodv for Vector2i for now. Meh.s @@ -1016,15 +1046,15 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_ return output; } -Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase) { +HashMap<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } // Create the rect to draw. @@ -1037,7 +1067,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start erase_pattern->set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); Ref<TileMapPattern> pattern = p_erase ? erase_pattern : selection_pattern; - Map<Vector2i, TileMapCell> err_output; + HashMap<Vector2i, TileMapCell> err_output; ERR_FAIL_COND_V(pattern->is_empty(), err_output); // Compute the offset to align things to the bottom or right. @@ -1045,9 +1075,9 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start bool valigned_bottom = p_end_cell.y < p_start_cell.y; Vector2i offset = Vector2i(aligned_right ? -(pattern->get_size().x - (rect.get_size().x % pattern->get_size().x)) : 0, valigned_bottom ? -(pattern->get_size().y - (rect.get_size().y % pattern->get_size().y)) : 0); - Map<Vector2i, TileMapCell> output; + HashMap<Vector2i, TileMapCell> output; if (!pattern->is_empty()) { - if (!p_erase && random_tile_checkbox->is_pressed()) { + if (!p_erase && random_tile_toggle->is_pressed()) { // Paint a random tile. for (int x = 0; x < rect.size.x; x++) { for (int y = 0; y < rect.size.y; y++) { @@ -1075,21 +1105,21 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start return output; } -Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase) { +HashMap<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } if (tile_map_layer < 0) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } - Map<Vector2i, TileMapCell> output; + HashMap<Vector2i, TileMapCell> output; ERR_FAIL_INDEX_V(tile_map_layer, tile_map->get_layers_count(), output); Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } // Get or create the pattern. @@ -1109,7 +1139,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i if (p_contiguous) { // Replace continuous tiles like the source. - Set<Vector2i> already_checked; + RBSet<Vector2i> already_checked; List<Vector2i> to_check; to_check.push_back(p_coords); while (!to_check.is_empty()) { @@ -1120,7 +1150,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i source_cell.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) && source_cell.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) && (source_cell.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) { - if (!p_erase && random_tile_checkbox->is_pressed()) { + if (!p_erase && random_tile_toggle->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); } else { @@ -1136,7 +1166,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i } // Get surrounding tiles (handles different tile shapes). - TypedArray<Vector2i> around = tile_map->get_surrounding_tiles(coords); + TypedArray<Vector2i> around = tile_map->get_surrounding_cells(coords); for (int i = 0; i < around.size(); i++) { to_check.push_back(around[i]); } @@ -1149,7 +1179,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i TypedArray<Vector2i> to_check; if (source_cell.source_id == TileSet::INVALID_SOURCE) { Rect2i rect = tile_map->get_used_rect(); - if (rect.has_no_area()) { + if (!rect.has_area()) { rect = Rect2i(p_coords, Vector2i(1, 1)); } for (int x = boundaries.position.x; x < boundaries.get_end().x; x++) { @@ -1166,7 +1196,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i source_cell.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) && source_cell.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) && (source_cell.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) { - if (!p_erase && random_tile_checkbox->is_pressed()) { + if (!p_erase && random_tile_toggle->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); } else { @@ -1210,19 +1240,20 @@ void TileMapEditorTilesPlugin::_stop_dragging() { Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); Vector2 mpos = xform.affine_inverse().xform(CanvasItemEditor::get_singleton()->get_viewport_control()->get_local_mouse_position()); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); switch (drag_type) { case DRAG_TYPE_SELECT: { undo_redo->create_action(TTR("Change selection")); undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); - if (!Input::get_singleton()->is_key_pressed(Key::SHIFT) && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { + if (!Input::get_singleton()->is_key_pressed(Key::SHIFT) && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) { tile_map_selection.clear(); } - Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos) - tile_map->world_to_map(drag_start_mouse_pos)).abs(); + Rect2i rect = Rect2i(tile_map->local_to_map(drag_start_mouse_pos), tile_map->local_to_map(mpos) - tile_map->local_to_map(drag_start_mouse_pos)).abs(); for (int x = rect.position.x; x <= rect.get_end().x; x++) { for (int y = rect.position.y; y <= rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); - if (Input::get_singleton()->is_key_pressed(Key::CTRL)) { + if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) { if (tile_map_selection.has(coords)) { tile_map_selection.erase(coords); } @@ -1246,32 +1277,34 @@ void TileMapEditorTilesPlugin::_stop_dragging() { tile_map->set_cell(tile_map_layer, kv.key, kv.value.source_id, kv.value.get_atlas_coords(), kv.value.alternative_tile); } - // Creating a pattern in the pattern list. - select_last_pattern = true; - int new_pattern_index = tile_set->get_patterns_count(); - undo_redo->create_action(TTR("Add TileSet pattern")); - undo_redo->add_do_method(*tile_set, "add_pattern", selection_pattern, new_pattern_index); - undo_redo->add_undo_method(*tile_set, "remove_pattern", new_pattern_index); - undo_redo->commit_action(); + if (!EditorNode::get_singleton()->is_resource_read_only(tile_set)) { + // Creating a pattern in the pattern list. + select_last_pattern = true; + int new_pattern_index = tile_set->get_patterns_count(); + undo_redo->create_action(TTR("Add TileSet pattern")); + undo_redo->add_do_method(*tile_set, "add_pattern", selection_pattern, new_pattern_index); + undo_redo->add_undo_method(*tile_set, "remove_pattern", new_pattern_index); + undo_redo->commit_action(); + } } else { // Get the top-left cell. Vector2i top_left; if (!tile_map_selection.is_empty()) { top_left = tile_map_selection.front()->get(); } - for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - top_left = top_left.min(E->get()); + for (const Vector2i &E : tile_map_selection) { + top_left = top_left.min(E); } // Get the offset from the mouse. - Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left); - offset = tile_map->world_to_map(mpos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset); + Vector2i offset = drag_start_mouse_pos - tile_map->map_to_local(top_left); + offset = tile_map->local_to_map(mpos - offset) - tile_map->local_to_map(drag_start_mouse_pos - offset); TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells(); // Build the list of cells to undo. Vector2i coords; - Map<Vector2i, TileMapCell> cells_undo; + HashMap<Vector2i, TileMapCell> cells_undo; for (int i = 0; i < selection_used_cells.size(); i++) { coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern); cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile); @@ -1280,7 +1313,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { } // Build the list of cells to do. - Map<Vector2i, TileMapCell> cells_do; + HashMap<Vector2i, TileMapCell> cells_do; for (int i = 0; i < selection_used_cells.size(); i++) { coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern); cells_do[coords] = TileMapCell(); @@ -1292,11 +1325,11 @@ void TileMapEditorTilesPlugin::_stop_dragging() { // Move the tiles. undo_redo->create_action(TTR("Move tiles")); - for (Map<Vector2i, TileMapCell>::Element *E = cells_do.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : cells_do) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } - for (Map<Vector2i, TileMapCell>::Element *E = cells_undo.front(); E; E = E->next()) { - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : cells_undo) { + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } // Update the selection. @@ -1311,18 +1344,38 @@ void TileMapEditorTilesPlugin::_stop_dragging() { } } break; case DRAG_TYPE_PICK: { - Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos) - tile_map->world_to_map(drag_start_mouse_pos)).abs(); + Rect2i rect = Rect2i(tile_map->local_to_map(drag_start_mouse_pos), tile_map->local_to_map(mpos) - tile_map->local_to_map(drag_start_mouse_pos)).abs(); rect.size += Vector2i(1, 1); + int picked_source = -1; TypedArray<Vector2i> coords_array; for (int x = rect.position.x; x < rect.get_end().x; x++) { for (int y = rect.position.y; y < rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); - if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) { + + int source = tile_map->get_cell_source_id(tile_map_layer, coords); + if (source != TileSet::INVALID_SOURCE) { coords_array.push_back(coords); + if (picked_source == -1) { + picked_source = source; + } else if (picked_source != source) { + picked_source = -2; + } + } + } + } + + if (picked_source >= 0) { + for (int i = 0; i < sources_list->get_item_count(); i++) { + if (int(sources_list->get_item_metadata(i)) == picked_source) { + sources_list->set_current(i); + TilesEditorPlugin::get_singleton()->set_sources_lists_current(i); + break; } } + sources_list->ensure_current_is_visible(); } + Ref<TileMapPattern> new_selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array); if (!new_selection_pattern->is_empty()) { selection_pattern = new_selection_pattern; @@ -1339,7 +1392,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { undo_redo->commit_action(false); } break; case DRAG_TYPE_LINE: { - Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, mpos, drag_erasing); + HashMap<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, mpos, drag_erasing); undo_redo->create_action(TTR("Paint tiles")); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { @@ -1351,7 +1404,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { undo_redo->commit_action(); } break; case DRAG_TYPE_RECT: { - Map<Vector2i, TileMapCell> to_draw = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos), drag_erasing); + HashMap<Vector2i, TileMapCell> to_draw = _draw_rect(tile_map->local_to_map(drag_start_mouse_pos), tile_map->local_to_map(mpos), drag_erasing); undo_redo->create_action(TTR("Paint tiles")); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { @@ -1375,7 +1428,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { undo_redo->create_action(TTR("Paste tiles")); TypedArray<Vector2i> used_cells = tile_map_clipboard->get_used_cells(); for (int i = 0; i < used_cells.size(); i++) { - Vector2i coords = tile_map->map_pattern(tile_map->world_to_map(mpos - mouse_offset), used_cells[i], tile_map_clipboard); + Vector2i coords = tile_map->map_pattern(tile_map->local_to_map(mpos - mouse_offset), used_cells[i], tile_map_clipboard); undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, coords, tile_map_clipboard->get_cell_source_id(used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(used_cells[i])); undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, coords, tile_map->get_cell_source_id(tile_map_layer, coords), tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords)); } @@ -1437,13 +1490,15 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { } // Selection if needed. - for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) { + for (RBSet<TileMapCell>::Element *E = tile_set_selection.front(); E;) { + RBSet<TileMapCell>::Element *N = E->next(); const TileMapCell *selected = &(E->get()); if (!tile_set->has_source(selected->source_id) || !tile_set->get_source(selected->source_id)->has_tile(selected->get_atlas_coords()) || !tile_set->get_source(selected->source_id)->has_alternative_tile(selected->get_atlas_coords(), selected->alternative_tile)) { tile_set_selection.erase(E); } + E = N; } if (!tile_map_selection.is_empty()) { @@ -1461,7 +1516,7 @@ void TileMapEditorTilesPlugin::_fix_invalid_tiles_in_tile_map_selection() { return; } - Set<Vector2i> to_remove; + RBSet<Vector2i> to_remove; for (Vector2i selected : tile_map_selection) { TileMapCell cell = tile_map->get_cell(tile_map_layer, selected); if (cell.source_id == TileSet::INVALID_SOURCE && cell.get_atlas_coords() == TileSetSource::INVALID_ATLAS_COORDS && cell.alternative_tile == TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { @@ -1473,6 +1528,11 @@ void TileMapEditorTilesPlugin::_fix_invalid_tiles_in_tile_map_selection() { tile_map_selection.erase(cell); } } +void TileMapEditorTilesPlugin::patterns_item_list_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index) { + if (p_mouse_button_index == MouseButton::LEFT) { + _update_selection_pattern_from_tileset_pattern_selection(); + } +} void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection() { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); @@ -1490,8 +1550,8 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection( selection_pattern.instantiate(); TypedArray<Vector2i> coords_array; - for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - coords_array.push_back(E->get()); + for (const Vector2i &E : tile_map_selection) { + coords_array.push_back(E); } selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array); } @@ -1514,9 +1574,9 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_tiles_sele selection_pattern.instantiate(); // Group per source. - Map<int, List<const TileMapCell *>> per_source; - for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) { - per_source[E->get().source_id].push_back(&(E->get())); + HashMap<int, List<const TileMapCell *>> per_source; + for (const TileMapCell &E : tile_set_selection) { + per_source[E.source_id].push_back(&(E)); } int vertical_offset = 0; @@ -1524,7 +1584,7 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_tiles_sele // Per source. List<const TileMapCell *> unorganized; Rect2i encompassing_rect_coords; - Map<Vector2i, const TileMapCell *> organized_pattern; + HashMap<Vector2i, const TileMapCell *> organized_pattern; TileSetSource *source = *tile_set->get_source(E_source.key); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); @@ -1539,12 +1599,12 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_tiles_sele } // Compute the encompassing rect for the organized pattern. - Map<Vector2i, const TileMapCell *>::Element *E_cell = organized_pattern.front(); + HashMap<Vector2i, const TileMapCell *>::Iterator E_cell = organized_pattern.begin(); if (E_cell) { - encompassing_rect_coords = Rect2i(E_cell->key(), Vector2i(1, 1)); - for (; E_cell; E_cell = E_cell->next()) { - encompassing_rect_coords.expand_to(E_cell->key() + Vector2i(1, 1)); - encompassing_rect_coords.expand_to(E_cell->key()); + encompassing_rect_coords = Rect2i(E_cell->key, Vector2i(1, 1)); + for (; E_cell; ++E_cell) { + encompassing_rect_coords.expand_to(E_cell->key + Vector2i(1, 1)); + encompassing_rect_coords.expand_to(E_cell->key); } } } else { @@ -1603,8 +1663,8 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern( } } _update_source_display(); - tile_atlas_control->update(); - alternative_tiles_control->update(); + tile_atlas_control->queue_redraw(); + alternative_tiles_control->queue_redraw(); } void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { @@ -1634,16 +1694,16 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { } // Draw the selection. - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); - for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) { - if (E->get().source_id == source_id && E->get().alternative_tile == 0) { - for (int frame = 0; frame < atlas->get_tile_animation_frames_count(E->get().get_atlas_coords()); frame++) { + for (const TileMapCell &E : tile_set_selection) { + if (E.source_id == source_id && E.alternative_tile == 0) { + for (int frame = 0; frame < atlas->get_tile_animation_frames_count(E.get_atlas_coords()); frame++) { Color color = selection_color; if (frame > 0) { color.a *= 0.3; } - tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get().get_atlas_coords(), frame), color, false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, atlas->get_tile_texture_region(E.get_atlas_coords(), frame), color); } } } @@ -1651,23 +1711,20 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { // Draw the hovered tile. if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0 && !tile_set_dragging_selection) { for (int frame = 0; frame < atlas->get_tile_animation_frames_count(hovered_tile.get_atlas_coords()); frame++) { - Color color = Color(1.0, 1.0, 1.0); - if (frame > 0) { - color.a *= 0.3; - } - tile_atlas_control->draw_rect(atlas->get_tile_texture_region(hovered_tile.get_atlas_coords(), frame), color, false); + Color color = Color(1.0, 0.8, 0.0, frame == 0 ? 0.6 : 0.3); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, atlas->get_tile_texture_region(hovered_tile.get_atlas_coords(), frame), color); } } // Draw the selection rect. if (tile_set_dragging_selection) { - Vector2i start_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_set_drag_start_mouse_pos); - Vector2i end_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Vector2i start_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_set_drag_start_mouse_pos, true); + Vector2i end_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true); Rect2i region = Rect2i(start_tile, end_tile - start_tile).abs(); region.size += Vector2i(1, 1); - Set<Vector2i> to_draw; + RBSet<Vector2i> to_draw; for (int x = region.position.x; x < region.get_end().x; x++) { for (int y = region.position.y; y < region.get_end().y; y++) { Vector2i tile = atlas->get_tile_at_coords(Vector2i(x, y)); @@ -1676,9 +1733,8 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { } } } - Color selection_rect_color = selection_color.lightened(0.2); - for (Set<Vector2i>::Element *E = to_draw.front(); E; E = E->next()) { - tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get()), selection_rect_color, false); + for (const Vector2i &E : to_draw) { + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, atlas->get_tile_texture_region(E)); } } } @@ -1687,8 +1743,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_mouse_exited() { hovered_tile.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; - tile_set_dragging_selection = false; - tile_atlas_control->update(); + tile_atlas_control->queue_redraw(); } void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEvent> &p_event) { @@ -1732,8 +1787,8 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - tile_atlas_control->update(); - alternative_tiles_control->update(); + tile_atlas_control->queue_redraw(); + alternative_tiles_control->queue_redraw(); } Ref<InputEventMouseButton> mb = p_event; @@ -1759,8 +1814,8 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven tile_set_selection.clear(); } // Compute the covered area. - Vector2i start_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_set_drag_start_mouse_pos); - Vector2i end_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Vector2i start_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_set_drag_start_mouse_pos, true); + Vector2i end_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true); if (start_tile != TileSetSource::INVALID_ATLAS_COORDS && end_tile != TileSetSource::INVALID_ATLAS_COORDS) { Rect2i region = Rect2i(start_tile, end_tile - start_tile).abs(); region.size += Vector2i(1, 1); @@ -1793,7 +1848,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven } tile_set_dragging_selection = false; } - tile_atlas_control->update(); + tile_atlas_control->queue_redraw(); } } @@ -1824,11 +1879,11 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() { } // Draw the selection. - for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) { - if (E->get().source_id == source_id && E->get().get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && E->get().alternative_tile > 0) { - Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E->get().get_atlas_coords(), E->get().alternative_tile); + for (const TileMapCell &E : tile_set_selection) { + if (E.source_id == source_id && E.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && E.alternative_tile > 0) { + Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E.get_atlas_coords(), E.alternative_tile); if (rect != Rect2i()) { - alternative_tiles_control->draw_rect(rect, Color(0.2, 0.2, 1.0), false); + TilesEditorPlugin::draw_selection_rect(alternative_tiles_control, rect); } } } @@ -1837,7 +1892,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() { if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile > 0) { Rect2i rect = tile_atlas_view->get_alternative_tile_rect(hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile); if (rect != Rect2i()) { - alternative_tiles_control->draw_rect(rect, Color(1.0, 1.0, 1.0), false); + TilesEditorPlugin::draw_selection_rect(alternative_tiles_control, rect, Color(1.0, 0.8, 0.0, 0.5)); } } } @@ -1846,8 +1901,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_mouse_exited() { hovered_tile.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; - tile_set_dragging_selection = false; - alternative_tiles_control->update(); + alternative_tiles_control->queue_redraw(); } void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event) { @@ -1890,8 +1944,8 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<In Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - tile_atlas_control->update(); - alternative_tiles_control->update(); + tile_atlas_control->queue_redraw(); + alternative_tiles_control->queue_redraw(); } Ref<InputEventMouseButton> mb = p_event; @@ -1911,8 +1965,8 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<In } _update_selection_pattern_from_tileset_tiles_selection(); } - tile_atlas_control->update(); - alternative_tiles_control->update(); + tile_atlas_control->queue_redraw(); + alternative_tiles_control->queue_redraw(); } } @@ -1928,8 +1982,8 @@ void TileMapEditorTilesPlugin::_set_tile_map_selection(const TypedArray<Vector2i TypedArray<Vector2i> TileMapEditorTilesPlugin::_get_tile_map_selection() const { TypedArray<Vector2i> output; - for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - output.push_back(E->get()); + for (const Vector2i &E : tile_map_selection) { + output.push_back(E); } return output; } @@ -1937,6 +1991,15 @@ TypedArray<Vector2i> TileMapEditorTilesPlugin::_get_tile_map_selection() const { void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer) { _stop_dragging(); // Avoids staying in a wrong drag state. + // Disable sort button if the tileset is read-only + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (tile_map) { + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (tile_set.is_valid()) { + source_sort_button->set_disabled(EditorNode::get_singleton()->is_resource_read_only(tile_set)); + } + } + if (tile_map_id != p_tile_map_id) { tile_map_id = p_tile_map_id; @@ -1950,6 +2013,15 @@ void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer tile_map_layer = p_tile_map_layer; } +void TileMapEditorTilesPlugin::_set_source_sort(int p_sort) { + for (int i = 0; i != TilesEditorPlugin::SOURCE_SORT_MAX; i++) { + source_sort_button->get_popup()->set_item_checked(i, (i == (int)p_sort)); + } + TilesEditorPlugin::get_singleton()->set_sorting_option(p_sort); + _update_tile_set_sources_list(); + EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "tile_source_sort", p_sort); +} + void TileMapEditorTilesPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileMapEditorTilesPlugin::_scene_thumbnail_done); ClassDB::bind_method(D_METHOD("_set_tile_map_selection", "selection"), &TileMapEditorTilesPlugin::_set_tile_map_selection); @@ -1957,12 +2029,14 @@ void TileMapEditorTilesPlugin::_bind_methods() { } TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { - CanvasItemEditor::get_singleton()->get_viewport_control()->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_mouse_exited_viewport)); + CanvasItemEditor::get_singleton() + ->get_viewport_control() + ->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_mouse_exited_viewport)); // --- Shortcuts --- - ED_SHORTCUT("tiles_editor/cut", TTR("Cut"), KeyModifierMask::CMD | Key::X); - ED_SHORTCUT("tiles_editor/copy", TTR("Copy"), KeyModifierMask::CMD | Key::C); - ED_SHORTCUT("tiles_editor/paste", TTR("Paste"), KeyModifierMask::CMD | Key::V); + ED_SHORTCUT("tiles_editor/cut", TTR("Cut"), KeyModifierMask::CMD_OR_CTRL | Key::X); + ED_SHORTCUT("tiles_editor/copy", TTR("Copy"), KeyModifierMask::CMD_OR_CTRL | Key::C); + ED_SHORTCUT("tiles_editor/paste", TTR("Paste"), KeyModifierMask::CMD_OR_CTRL | Key::V); ED_SHORTCUT("tiles_editor/cancel", TTR("Cancel"), Key::ESCAPE); ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), Key::KEY_DELETE); @@ -1972,7 +2046,6 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { // --- Toolbar --- toolbar = memnew(HBoxContainer); - toolbar->set_h_size_flags(Control::SIZE_EXPAND_FILL); HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer); @@ -1982,7 +2055,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { select_tool_button->set_flat(true); select_tool_button->set_toggle_mode(true); select_tool_button->set_button_group(tool_buttons_group); - select_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/selection_tool", "Selection", Key::S)); + select_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/selection_tool", TTR("Selection"), Key::S)); select_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(select_tool_button); @@ -1990,8 +2063,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { paint_tool_button->set_flat(true); paint_tool_button->set_toggle_mode(true); paint_tool_button->set_button_group(tool_buttons_group); - paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", Key::D)); - paint_tool_button->set_tooltip(TTR("Shift: Draw line.") + "\n" + TTR("Shift+Ctrl: Draw rectangle.")); + paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", TTR("Paint"), Key::D)); + paint_tool_button->set_tooltip_text(TTR("Shift: Draw line.") + "\n" + TTR("Shift+Ctrl: Draw rectangle.")); paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(paint_tool_button); @@ -1999,7 +2072,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { line_tool_button->set_flat(true); line_tool_button->set_toggle_mode(true); line_tool_button->set_button_group(tool_buttons_group); - line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", "Line", Key::L)); + // TRANSLATORS: This refers to the line tool in the tilemap editor. + line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", TTR("Line", "Tool"), Key::L)); line_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(line_tool_button); @@ -2007,7 +2081,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { rect_tool_button->set_flat(true); rect_tool_button->set_toggle_mode(true); rect_tool_button->set_button_group(tool_buttons_group); - rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", "Rect", Key::R)); + rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", TTR("Rect"), Key::R)); rect_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(rect_tool_button); @@ -2015,7 +2089,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { bucket_tool_button->set_flat(true); bucket_tool_button->set_toggle_mode(true); bucket_tool_button->set_button_group(tool_buttons_group); - bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", "Bucket", Key::B)); + bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", TTR("Bucket"), Key::B)); bucket_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(bucket_tool_button); toolbar->add_child(tilemap_tiles_tools_buttons); @@ -2031,8 +2105,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { picker_button = memnew(Button); picker_button->set_flat(true); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", Key::P)); - picker_button->set_tooltip(TTR("Alternatively hold Ctrl with other tools to pick tile.")); + picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P)); + picker_button->set_tooltip_text(TTR("Alternatively hold Ctrl with other tools to pick tile.")); picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(picker_button); @@ -2040,8 +2114,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { erase_button = memnew(Button); erase_button->set_flat(true); erase_button->set_toggle_mode(true); - erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", Key::E)); - erase_button->set_tooltip(TTR("Alternatively use RMB to erase tiles.")); + erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", TTR("Eraser"), Key::E)); + erase_button->set_tooltip_text(TTR("Alternatively use RMB to erase tiles.")); erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(erase_button); @@ -2057,26 +2131,30 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { tools_settings->add_child(bucket_contiguous_checkbox); // Random tile checkbox. - random_tile_checkbox = memnew(CheckBox); - random_tile_checkbox->set_flat(true); - random_tile_checkbox->set_text(TTR("Place Random Tile")); - random_tile_checkbox->connect("toggled", callable_mp(this, &TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled)); - tools_settings->add_child(random_tile_checkbox); + random_tile_toggle = memnew(Button); + random_tile_toggle->set_flat(true); + random_tile_toggle->set_toggle_mode(true); + random_tile_toggle->set_tooltip_text(TTR("Place Random Tile")); + random_tile_toggle->connect("toggled", callable_mp(this, &TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled)); + tools_settings->add_child(random_tile_toggle); // Random tile scattering. + scatter_controls_container = memnew(HBoxContainer); + scatter_label = memnew(Label); - scatter_label->set_tooltip(TTR("Defines the probability of painting nothing instead of a randomly selected tile.")); + scatter_label->set_tooltip_text(TTR("Modifies the chance of painting nothing instead of a randomly selected tile.")); scatter_label->set_text(TTR("Scattering:")); - tools_settings->add_child(scatter_label); + scatter_controls_container->add_child(scatter_label); scatter_spinbox = memnew(SpinBox); scatter_spinbox->set_min(0.0); scatter_spinbox->set_max(1000); scatter_spinbox->set_step(0.001); - scatter_spinbox->set_tooltip(TTR("Defines the probability of painting nothing instead of a randomly selected tile.")); + scatter_spinbox->set_tooltip_text(TTR("Modifies the chance of painting nothing instead of a randomly selected tile.")); scatter_spinbox->get_line_edit()->add_theme_constant_override("minimum_character_width", 4); scatter_spinbox->connect("value_changed", callable_mp(this, &TileMapEditorTilesPlugin::_on_scattering_spinbox_changed)); - tools_settings->add_child(scatter_spinbox); + scatter_controls_container->add_child(scatter_spinbox); + tools_settings->add_child(scatter_controls_container); _on_random_tile_checkbox_toggled(false); @@ -2106,17 +2184,44 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { atlas_sources_split_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); tiles_bottom_panel->add_child(atlas_sources_split_container); + VBoxContainer *split_container_left_side = memnew(VBoxContainer); + split_container_left_side->set_h_size_flags(Control::SIZE_EXPAND_FILL); + split_container_left_side->set_v_size_flags(Control::SIZE_EXPAND_FILL); + split_container_left_side->set_stretch_ratio(0.25); + split_container_left_side->set_custom_minimum_size(Size2(70, 0) * EDSCALE); + atlas_sources_split_container->add_child(split_container_left_side); + + HBoxContainer *sources_bottom_actions = memnew(HBoxContainer); + sources_bottom_actions->set_alignment(HBoxContainer::ALIGNMENT_END); + + source_sort_button = memnew(MenuButton); + source_sort_button->set_flat(true); + source_sort_button->set_tooltip_text(TTR("Sort sources")); + + PopupMenu *p = source_sort_button->get_popup(); + p->connect("id_pressed", callable_mp(this, &TileMapEditorTilesPlugin::_set_source_sort)); + p->add_radio_check_item(TTR("Sort by ID (Ascending)"), TilesEditorPlugin::SOURCE_SORT_ID); + p->add_radio_check_item(TTR("Sort by ID (Descending)"), TilesEditorPlugin::SOURCE_SORT_ID_REVERSE); + p->add_radio_check_item(TTR("Sort by Name (Ascending)"), TilesEditorPlugin::SOURCE_SORT_NAME); + p->add_radio_check_item(TTR("Sort by Name (Descending)"), TilesEditorPlugin::SOURCE_SORT_NAME_REVERSE); + p->set_item_checked(TilesEditorPlugin::SOURCE_SORT_ID, true); + sources_bottom_actions->add_child(source_sort_button); + sources_list = memnew(ItemList); - sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE); + sources_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE); sources_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); + sources_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); sources_list->set_stretch_ratio(0.25); - sources_list->set_custom_minimum_size(Size2i(70, 0) * EDSCALE); + sources_list->set_custom_minimum_size(Size2(70, 0) * EDSCALE); sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_fix_selected_and_hovered).unbind(1)); sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_source_display).unbind(1)); sources_list->connect("item_selected", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::set_sources_lists_current)); - sources_list->connect("visibility_changed", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::synchronize_sources_list), varray(sources_list)); - atlas_sources_split_container->add_child(sources_list); + sources_list->connect("visibility_changed", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::synchronize_sources_list).bind(sources_list, source_sort_button)); + sources_list->add_user_signal(MethodInfo("sort_request")); + sources_list->connect("sort_request", callable_mp(this, &TileMapEditorTilesPlugin::_update_tile_set_sources_list)); + split_container_left_side->add_child(sources_list); + split_container_left_side->add_child(sources_bottom_actions); // Tile atlas source. tile_atlas_view = memnew(TileAtlasView); @@ -2143,10 +2248,9 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { scene_tiles_list = memnew(ItemList); scene_tiles_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); scene_tiles_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); - scene_tiles_list->set_drag_forwarding(this); scene_tiles_list->set_select_mode(ItemList::SELECT_MULTI); scene_tiles_list->connect("multi_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_multi_selected)); - scene_tiles_list->connect("nothing_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_nothing_selected)); + scene_tiles_list->connect("empty_clicked", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_lmb_empty_clicked)); scene_tiles_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); atlas_sources_split_container->add_child(scene_tiles_list); @@ -2175,12 +2279,13 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { patterns_item_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); patterns_item_list->connect("gui_input", callable_mp(this, &TileMapEditorTilesPlugin::_patterns_item_list_gui_input)); patterns_item_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection).unbind(1)); - patterns_item_list->connect("item_activated", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection)); - patterns_item_list->connect("nothing_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection)); + patterns_item_list->connect("item_activated", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection).unbind(1)); + patterns_item_list->connect("empty_clicked", callable_mp(this, &TileMapEditorTilesPlugin::patterns_item_list_empty_clicked)); patterns_bottom_panel->add_child(patterns_item_list); patterns_help_label = memnew(Label); patterns_help_label->set_text(TTR("Drag and drop or paste a TileMap selection here to store a pattern.")); + patterns_help_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); patterns_help_label->set_anchors_and_offsets_preset(Control::PRESET_CENTER); patterns_item_list->add_child(patterns_help_label); @@ -2237,161 +2342,138 @@ Vector<TileMapEditorPlugin::TabData> TileMapEditorTerrainsPlugin::get_tabs() con return tabs; } -Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map<Vector2i, TileSet::TerrainsPattern> &p_to_paint, int p_terrain_set) const { +HashMap<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrain_path_or_connect(const Vector<Vector2i> &p_to_paint, int p_terrain_set, int p_terrain, bool p_connect) const { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Map<Vector2i, TileMapCell>(); - } - - Map<Vector2i, TileMapCell> output; - - // Add the constraints from the added tiles. - Set<TileMap::TerrainConstraint> added_tiles_constraints_set; - for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) { - Vector2i coords = E_to_paint.key; - TileSet::TerrainsPattern terrains_pattern = E_to_paint.value; - - Set<TileMap::TerrainConstraint> cell_constraints = tile_map->get_terrain_constraints_from_added_tile(coords, p_terrain_set, terrains_pattern); - for (Set<TileMap::TerrainConstraint>::Element *E = cell_constraints.front(); E; E = E->next()) { - added_tiles_constraints_set.insert(E->get()); - } + return HashMap<Vector2i, TileMapCell>(); } - // Build the list of potential tiles to replace. - Set<Vector2i> potential_to_replace; - for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) { - Vector2i coords = E_to_paint.key; - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - if (tile_map->is_existing_neighbor(TileSet::CellNeighbor(i))) { - Vector2i neighbor = tile_map->get_neighbor_cell(coords, TileSet::CellNeighbor(i)); - if (!p_to_paint.has(neighbor)) { - potential_to_replace.insert(neighbor); - } - } - } + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output; + if (p_connect) { + terrain_fill_output = tile_map->terrain_fill_connect(tile_map_layer, p_to_paint, p_terrain_set, p_terrain, false); + } else { + terrain_fill_output = tile_map->terrain_fill_path(tile_map_layer, p_to_paint, p_terrain_set, p_terrain, false); } - // Set of tiles to replace - Set<Vector2i> to_replace; - - // Add the central tiles to the one to replace. - for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) { - to_replace.insert(E_to_paint.key); + // Make the painted path a set for faster lookups + HashSet<Vector2i> painted_set; + for (Vector2i coords : p_to_paint) { + painted_set.insert(coords); } - // Add the constraints from the surroundings of the modified areas. - Set<TileMap::TerrainConstraint> removed_cells_constraints_set; - bool to_replace_modified = true; - while (to_replace_modified) { - // Get the constraints from the removed cells. - removed_cells_constraints_set = tile_map->get_terrain_constraints_from_removed_cells_list(tile_map_layer, to_replace, p_terrain_set); - - // Filter the sources to make sure they are in the potential_to_replace. - Map<TileMap::TerrainConstraint, Set<Vector2i>> per_constraint_tiles; - for (Set<TileMap::TerrainConstraint>::Element *E = removed_cells_constraints_set.front(); E; E = E->next()) { - Map<Vector2i, TileSet::CellNeighbor> sources_of_constraint = E->get().get_overlapping_coords_and_peering_bits(); - for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_source_tile_of_constraint : sources_of_constraint) { - if (potential_to_replace.has(E_source_tile_of_constraint.key)) { - per_constraint_tiles[E->get()].insert(E_source_tile_of_constraint.key); - } - } - } - - to_replace_modified = false; - for (Set<TileMap::TerrainConstraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) { - TileMap::TerrainConstraint c = E->get(); - // Check if we have a conflict in constraints. - if (removed_cells_constraints_set.has(c) && removed_cells_constraints_set.find(c)->get().get_terrain() != c.get_terrain()) { - // If we do, we search for a neighbor to remove. - if (per_constraint_tiles.has(c) && !per_constraint_tiles[c].is_empty()) { - // Remove it. - Vector2i to_add_to_remove = per_constraint_tiles[c].front()->get(); - potential_to_replace.erase(to_add_to_remove); - to_replace.insert(to_add_to_remove); - to_replace_modified = true; - for (KeyValue<TileMap::TerrainConstraint, Set<Vector2i>> &E_source_tiles_of_constraint : per_constraint_tiles) { - E_source_tiles_of_constraint.value.erase(to_add_to_remove); + HashMap<Vector2i, TileMapCell> output; + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : terrain_fill_output) { + if (painted_set.has(kv.key)) { + // Paint a random tile with the correct terrain for the painted path. + output[kv.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); + } else { + // Avoids updating the painted path from the output if the new pattern is the same as before. + TileSet::TerrainsPattern in_map_terrain_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); + TileMapCell cell = tile_map->get_cell(tile_map_layer, kv.key); + if (cell.source_id != TileSet::INVALID_SOURCE) { + TileSetSource *source = *tile_set->get_source(cell.source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get tile data. + TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { + in_map_terrain_pattern = tile_data->get_terrains_pattern(); } - break; } } + if (in_map_terrain_pattern != kv.value) { + output[kv.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); + } } } + return output; +} - // Combine all constraints together. - Set<TileMap::TerrainConstraint> constraints = removed_cells_constraints_set; - for (Set<TileMap::TerrainConstraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) { - constraints.insert(E->get()); +HashMap<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrain_pattern(const Vector<Vector2i> &p_to_paint, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return HashMap<Vector2i, TileMapCell>(); } - // Remove the central tiles from the ones to replace. - for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) { - to_replace.erase(E_to_paint.key); + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return HashMap<Vector2i, TileMapCell>(); } - // Run WFC to fill the holes with the constraints. - Map<Vector2i, TileSet::TerrainsPattern> wfc_output = tile_map->terrain_wave_function_collapse(to_replace, p_terrain_set, constraints); + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = tile_map->terrain_fill_pattern(tile_map_layer, p_to_paint, p_terrain_set, p_terrains_pattern, false); - // Actually paint the tiles. - for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) { - output[E_to_paint.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E_to_paint.value); + // Make the painted path a set for faster lookups + HashSet<Vector2i> painted_set; + for (Vector2i coords : p_to_paint) { + painted_set.insert(coords); } - // Use the WFC run for the output. - for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E : wfc_output) { - output[E.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E.value); + HashMap<Vector2i, TileMapCell> output; + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : terrain_fill_output) { + if (painted_set.has(kv.key)) { + // Paint a random tile with the correct terrain for the painted path. + output[kv.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); + } else { + // Avoids updating the painted path from the output if the new pattern is the same as before. + TileSet::TerrainsPattern in_map_terrain_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); + TileMapCell cell = tile_map->get_cell(tile_map_layer, kv.key); + if (cell.source_id != TileSet::INVALID_SOURCE) { + TileSetSource *source = *tile_set->get_source(cell.source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get tile data. + TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { + in_map_terrain_pattern = tile_data->get_terrains_pattern(); + } + } + } + if (in_map_terrain_pattern != kv.value) { + output[kv.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); + } + } } - return output; } -Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_line(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase) { +HashMap<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_line(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } - TileSet::TerrainsPattern terrains_pattern; if (p_erase) { - terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); + return _draw_terrain_pattern(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, TileSet::TerrainsPattern(*tile_set, selected_terrain_set)); } else { - terrains_pattern = selected_terrains_pattern; - } - - Vector<Vector2i> line = TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell); - Map<Vector2i, TileSet::TerrainsPattern> to_draw; - for (int i = 0; i < line.size(); i++) { - to_draw[line[i]] = terrains_pattern; + if (selected_type == SELECTED_TYPE_CONNECT) { + return _draw_terrain_path_or_connect(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrain, true); + } else if (selected_type == SELECTED_TYPE_PATH) { + return _draw_terrain_path_or_connect(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrain, false); + } else { // SELECTED_TYPE_PATTERN + return _draw_terrain_pattern(TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell), selected_terrain_set, selected_terrains_pattern); + } } - return _draw_terrains(to_draw, selected_terrain_set); } -Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase) { +HashMap<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Map<Vector2i, TileMapCell>(); - } - - TileSet::TerrainsPattern terrains_pattern; - if (p_erase) { - terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); - } else { - terrains_pattern = selected_terrains_pattern; + return HashMap<Vector2i, TileMapCell>(); } Rect2i rect; @@ -2399,24 +2481,33 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_rect(Vector2i p_st rect.set_end(p_end_cell); rect = rect.abs(); - Map<Vector2i, TileSet::TerrainsPattern> to_draw; + Vector<Vector2i> to_draw; for (int x = rect.position.x; x <= rect.get_end().x; x++) { for (int y = rect.position.y; y <= rect.get_end().y; y++) { - to_draw[Vector2i(x, y)] = terrains_pattern; + to_draw.append(Vector2i(x, y)); + } + } + + if (p_erase) { + return _draw_terrain_pattern(to_draw, selected_terrain_set, TileSet::TerrainsPattern(*tile_set, selected_terrain_set)); + } else { + if (selected_type == SELECTED_TYPE_CONNECT || selected_type == SELECTED_TYPE_PATH) { + return _draw_terrain_path_or_connect(to_draw, selected_terrain_set, selected_terrain, true); + } else { // SELECTED_TYPE_PATTERN + return _draw_terrain_pattern(to_draw, selected_terrain_set, selected_terrains_pattern); } } - return _draw_terrains(to_draw, selected_terrain_set); } -Set<Vector2i> TileMapEditorTerrainsPlugin::_get_cells_for_bucket_fill(Vector2i p_coords, bool p_contiguous) { +RBSet<Vector2i> TileMapEditorTerrainsPlugin::_get_cells_for_bucket_fill(Vector2i p_coords, bool p_contiguous) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { - return Set<Vector2i>(); + return RBSet<Vector2i>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Set<Vector2i>(); + return RBSet<Vector2i>(); } TileMapCell source_cell = tile_map->get_cell(tile_map_layer, p_coords); @@ -2427,10 +2518,10 @@ Set<Vector2i> TileMapEditorTerrainsPlugin::_get_cells_for_bucket_fill(Vector2i p Ref<TileSetSource> source = tile_set->get_source(source_cell.source_id); Ref<TileSetAtlasSource> atlas_source = source; if (atlas_source.is_valid()) { - tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(source_cell.get_atlas_coords(), source_cell.alternative_tile)); + tile_data = atlas_source->get_tile_data(source_cell.get_atlas_coords(), source_cell.alternative_tile); } if (!tile_data) { - return Set<Vector2i>(); + return RBSet<Vector2i>(); } source_pattern = tile_data->get_terrains_pattern(); } @@ -2441,10 +2532,10 @@ Set<Vector2i> TileMapEditorTerrainsPlugin::_get_cells_for_bucket_fill(Vector2i p boundaries = tile_map->get_used_rect(); } - Set<Vector2i> output; + RBSet<Vector2i> output; if (p_contiguous) { // Replace continuous tiles like the source. - Set<Vector2i> already_checked; + RBSet<Vector2i> already_checked; List<Vector2i> to_check; to_check.push_back(p_coords); while (!to_check.is_empty()) { @@ -2458,7 +2549,7 @@ Set<Vector2i> TileMapEditorTerrainsPlugin::_get_cells_for_bucket_fill(Vector2i p Ref<TileSetSource> source = tile_set->get_source(tile_map->get_cell_source_id(tile_map_layer, coords)); Ref<TileSetAtlasSource> atlas_source = source; if (atlas_source.is_valid()) { - tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords))); + tile_data = atlas_source->get_tile_data(tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords)); } if (tile_data) { candidate_pattern = tile_data->get_terrains_pattern(); @@ -2470,7 +2561,7 @@ Set<Vector2i> TileMapEditorTerrainsPlugin::_get_cells_for_bucket_fill(Vector2i p output.insert(coords); // Get surrounding tiles (handles different tile shapes). - TypedArray<Vector2i> around = tile_map->get_surrounding_tiles(coords); + TypedArray<Vector2i> around = tile_map->get_surrounding_cells(coords); for (int i = 0; i < around.size(); i++) { to_check.push_back(around[i]); } @@ -2483,7 +2574,7 @@ Set<Vector2i> TileMapEditorTerrainsPlugin::_get_cells_for_bucket_fill(Vector2i p TypedArray<Vector2i> to_check; if (source_cell.source_id == TileSet::INVALID_SOURCE) { Rect2i rect = tile_map->get_used_rect(); - if (rect.has_no_area()) { + if (!rect.has_area()) { rect = Rect2i(p_coords, Vector2i(1, 1)); } for (int x = boundaries.position.x; x < boundaries.get_end().x; x++) { @@ -2503,7 +2594,7 @@ Set<Vector2i> TileMapEditorTerrainsPlugin::_get_cells_for_bucket_fill(Vector2i p Ref<TileSetSource> source = tile_set->get_source(tile_map->get_cell_source_id(tile_map_layer, coords)); Ref<TileSetAtlasSource> atlas_source = source; if (atlas_source.is_valid()) { - tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords))); + tile_data = atlas_source->get_tile_data(tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords)); } if (tile_data) { candidate_pattern = tile_data->get_terrains_pattern(); @@ -2519,31 +2610,32 @@ Set<Vector2i> TileMapEditorTerrainsPlugin::_get_cells_for_bucket_fill(Vector2i p return output; } -Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase) { +HashMap<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Map<Vector2i, TileMapCell>(); + return HashMap<Vector2i, TileMapCell>(); } - TileSet::TerrainsPattern terrains_pattern; - if (p_erase) { - terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); - } else { - terrains_pattern = selected_terrains_pattern; + RBSet<Vector2i> cells_to_draw = _get_cells_for_bucket_fill(p_coords, p_contiguous); + Vector<Vector2i> cells_to_draw_as_vector; + for (Vector2i cell : cells_to_draw) { + cells_to_draw_as_vector.append(cell); } - Set<Vector2i> cells_to_draw = _get_cells_for_bucket_fill(p_coords, p_contiguous); - Map<Vector2i, TileSet::TerrainsPattern> to_draw; - for (const Vector2i &coords : cells_to_draw) { - to_draw[coords] = terrains_pattern; + if (p_erase) { + return _draw_terrain_pattern(cells_to_draw_as_vector, selected_terrain_set, TileSet::TerrainsPattern(*tile_set, selected_terrain_set)); + } else { + if (selected_type == SELECTED_TYPE_CONNECT || selected_type == SELECTED_TYPE_PATH) { + return _draw_terrain_path_or_connect(cells_to_draw_as_vector, selected_terrain_set, selected_terrain, true); + } else { // SELECTED_TYPE_PATTERN + return _draw_terrain_pattern(cells_to_draw_as_vector, selected_terrain_set, selected_terrains_pattern); + } } - - return _draw_terrains(to_draw, selected_terrain_set); } void TileMapEditorTerrainsPlugin::_stop_dragging() { @@ -2560,16 +2652,17 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() { Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); Vector2 mpos = xform.affine_inverse().xform(CanvasItemEditor::get_singleton()->get_viewport_control()->get_local_mouse_position()); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); switch (drag_type) { case DRAG_TYPE_PICK: { - Vector2i coords = tile_map->world_to_map(mpos); + Vector2i coords = tile_map->local_to_map(mpos); TileMapCell cell = tile_map->get_cell(tile_map_layer, coords); TileData *tile_data = nullptr; Ref<TileSetSource> source = tile_set->get_source(cell.source_id); Ref<TileSetAtlasSource> atlas_source = source; if (atlas_source.is_valid()) { - tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile)); + tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); } if (tile_data) { @@ -2612,11 +2705,13 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() { if (tree_item) { for (int i = 0; i < terrains_tile_list->get_item_count(); i++) { Dictionary metadata_dict = terrains_tile_list->get_item_metadata(i); - TileSet::TerrainsPattern in_meta_terrains_pattern(*tile_set, new_terrain_set); - in_meta_terrains_pattern.set_terrains_from_array(metadata_dict["terrains_pattern"]); - if (in_meta_terrains_pattern == terrains_pattern) { - terrains_tile_list->select(i); - break; + if (int(metadata_dict["type"]) == SELECTED_TYPE_PATTERN) { + TileSet::TerrainsPattern in_meta_terrains_pattern(*tile_set, new_terrain_set); + in_meta_terrains_pattern.from_array(metadata_dict["terrains_pattern"]); + if (in_meta_terrains_pattern == terrains_pattern) { + terrains_tile_list->select(i); + break; + } } } } else { @@ -2634,7 +2729,7 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() { undo_redo->commit_action(false); } break; case DRAG_TYPE_LINE: { - Map<Vector2i, TileMapCell> to_draw = _draw_line(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos), drag_erasing); + HashMap<Vector2i, TileMapCell> to_draw = _draw_line(tile_map->local_to_map(drag_start_mouse_pos), tile_map->local_to_map(mpos), drag_erasing); undo_redo->create_action(TTR("Paint terrain")); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { @@ -2646,7 +2741,7 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() { undo_redo->commit_action(); } break; case DRAG_TYPE_RECT: { - Map<Vector2i, TileMapCell> to_draw = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos), drag_erasing); + HashMap<Vector2i, TileMapCell> to_draw = _draw_rect(tile_map->local_to_map(drag_start_mouse_pos), tile_map->local_to_map(mpos), drag_erasing); undo_redo->create_action(TTR("Paint terrain")); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { @@ -2689,22 +2784,33 @@ void TileMapEditorTerrainsPlugin::_update_selection() { } // Get the selected terrain. - selected_terrains_pattern = TileSet::TerrainsPattern(); selected_terrain_set = -1; + selected_terrains_pattern = TileSet::TerrainsPattern(); TreeItem *selected_tree_item = terrains_tree->get_selected(); if (selected_tree_item && selected_tree_item->get_metadata(0)) { Dictionary metadata_dict = selected_tree_item->get_metadata(0); // Selected terrain selected_terrain_set = metadata_dict["terrain_set"]; + selected_terrain = metadata_dict["terrain_id"]; - // Selected tile + // Selected mode/terrain pattern if (erase_button->is_pressed()) { + selected_type = SELECTED_TYPE_PATTERN; selected_terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); } else if (terrains_tile_list->is_anything_selected()) { metadata_dict = terrains_tile_list->get_item_metadata(terrains_tile_list->get_selected_items()[0]); - selected_terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); - selected_terrains_pattern.set_terrains_from_array(metadata_dict["terrains_pattern"]); + if (int(metadata_dict["type"]) == SELECTED_TYPE_CONNECT) { + selected_type = SELECTED_TYPE_CONNECT; + } else if (int(metadata_dict["type"]) == SELECTED_TYPE_PATH) { + selected_type = SELECTED_TYPE_PATH; + } else if (int(metadata_dict["type"]) == SELECTED_TYPE_PATTERN) { + selected_type = SELECTED_TYPE_PATTERN; + selected_terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); + selected_terrains_pattern.from_array(metadata_dict["terrains_pattern"]); + } else { + ERR_FAIL(); + } } } } @@ -2745,7 +2851,7 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> switch (drag_type) { case DRAG_TYPE_PAINT: { if (selected_terrain_set >= 0) { - Map<Vector2i, TileMapCell> to_draw = _draw_line(tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos), drag_erasing); + HashMap<Vector2i, TileMapCell> to_draw = _draw_line(tile_map->local_to_map(drag_last_mouse_pos), tile_map->local_to_map(mpos), drag_erasing); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { if (!drag_modified.has(E.key)) { drag_modified[E.key] = tile_map->get_cell(tile_map_layer, E.key); @@ -2780,8 +2886,8 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> drag_type = DRAG_TYPE_PICK; } else { // Paint otherwise. - if (tool_buttons_group->get_pressed_button() == paint_tool_button && !Input::get_singleton()->is_key_pressed(Key::CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT)) { - if (selected_terrain_set < 0 || !selected_terrains_pattern.is_valid()) { + if (tool_buttons_group->get_pressed_button() == paint_tool_button && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT)) { + if (selected_terrain_set < 0 || selected_terrain < 0 || (selected_type == SELECTED_TYPE_PATTERN && !selected_terrains_pattern.is_valid())) { return true; } @@ -2789,37 +2895,37 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> drag_start_mouse_pos = mpos; drag_modified.clear(); - Vector2i cell = tile_map->world_to_map(mpos); - Map<Vector2i, TileMapCell> to_draw = _draw_line(cell, cell, drag_erasing); + Vector2i cell = tile_map->local_to_map(mpos); + HashMap<Vector2i, TileMapCell> to_draw = _draw_line(cell, cell, drag_erasing); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { drag_modified[E.key] = tile_map->get_cell(tile_map_layer, E.key); tile_map->set_cell(tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } - } else if (tool_buttons_group->get_pressed_button() == line_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(Key::SHIFT) && !Input::get_singleton()->is_key_pressed(Key::CTRL))) { - if (selected_terrain_set < 0 || !selected_terrains_pattern.is_valid()) { + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(Key::SHIFT) && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL))) { + if (selected_terrain_set < 0 || selected_terrain < 0 || (selected_type == SELECTED_TYPE_PATTERN && !selected_terrains_pattern.is_valid())) { return true; } drag_type = DRAG_TYPE_LINE; drag_start_mouse_pos = mpos; drag_modified.clear(); - } else if (tool_buttons_group->get_pressed_button() == rect_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(Key::SHIFT) && Input::get_singleton()->is_key_pressed(Key::CTRL))) { - if (selected_terrain_set < 0 || !selected_terrains_pattern.is_valid()) { + } else if (tool_buttons_group->get_pressed_button() == rect_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(Key::SHIFT) && Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL))) { + if (selected_terrain_set < 0 || selected_terrain < 0 || (selected_type == SELECTED_TYPE_PATTERN && !selected_terrains_pattern.is_valid())) { return true; } drag_type = DRAG_TYPE_RECT; drag_start_mouse_pos = mpos; drag_modified.clear(); } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) { - if (selected_terrain_set < 0 || !selected_terrains_pattern.is_valid()) { + if (selected_terrain_set < 0 || selected_terrain < 0 || (selected_type == SELECTED_TYPE_PATTERN && !selected_terrains_pattern.is_valid())) { return true; } drag_type = DRAG_TYPE_BUCKET; drag_start_mouse_pos = mpos; drag_modified.clear(); - Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); + Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->local_to_map(drag_last_mouse_pos), tile_map->local_to_map(mpos)); for (int i = 0; i < line.size(); i++) { if (!drag_modified.has(line[i])) { - Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_contiguous_checkbox->is_pressed(), drag_erasing); + HashMap<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_contiguous_checkbox->is_pressed(), drag_erasing); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { continue; @@ -2875,31 +2981,31 @@ void TileMapEditorTerrainsPlugin::forward_canvas_draw_over_viewport(Control *p_o // Handle the preview of the tiles to be placed. if (main_vbox_container->is_visible_in_tree() && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered. - Set<Vector2i> preview; + RBSet<Vector2i> preview; Rect2i drawn_grid_rect; if (drag_type == DRAG_TYPE_PICK) { // Draw the area being picked. - Vector2i coords = tile_map->world_to_map(drag_last_mouse_pos); + Vector2i coords = tile_map->local_to_map(drag_last_mouse_pos); if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) { Transform2D tile_xform; - tile_xform.set_origin(tile_map->map_to_world(coords)); + tile_xform.set_origin(tile_map->map_to_local(coords)); tile_xform.set_scale(tile_shape_size); tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0), false); } - } else if (!picker_button->is_pressed() && !(drag_type == DRAG_TYPE_NONE && Input::get_singleton()->is_key_pressed(Key::CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { + } else if (!picker_button->is_pressed() && !(drag_type == DRAG_TYPE_NONE && Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { bool expand_grid = false; if (tool_buttons_group->get_pressed_button() == paint_tool_button && drag_type == DRAG_TYPE_NONE) { // Preview for a single tile. - preview.insert(tile_map->world_to_map(drag_last_mouse_pos)); + preview.insert(tile_map->local_to_map(drag_last_mouse_pos)); expand_grid = true; } else if (tool_buttons_group->get_pressed_button() == line_tool_button || drag_type == DRAG_TYPE_LINE) { if (drag_type == DRAG_TYPE_NONE) { // Preview for a single tile. - preview.insert(tile_map->world_to_map(drag_last_mouse_pos)); + preview.insert(tile_map->local_to_map(drag_last_mouse_pos)); } else if (drag_type == DRAG_TYPE_LINE) { // Preview for a line. - Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos)); + Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->local_to_map(drag_start_mouse_pos), tile_map->local_to_map(drag_last_mouse_pos)); for (int i = 0; i < line.size(); i++) { preview.insert(line[i]); } @@ -2908,11 +3014,11 @@ void TileMapEditorTerrainsPlugin::forward_canvas_draw_over_viewport(Control *p_o } else if (drag_type == DRAG_TYPE_RECT) { // Preview for a rect. Rect2i rect; - rect.set_position(tile_map->world_to_map(drag_start_mouse_pos)); - rect.set_end(tile_map->world_to_map(drag_last_mouse_pos)); + rect.set_position(tile_map->local_to_map(drag_start_mouse_pos)); + rect.set_end(tile_map->local_to_map(drag_last_mouse_pos)); rect = rect.abs(); - Map<Vector2i, TileSet::TerrainsPattern> to_draw; + HashMap<Vector2i, TileSet::TerrainsPattern> to_draw; for (int x = rect.position.x; x <= rect.get_end().x; x++) { for (int y = rect.position.y; y <= rect.get_end().y; y++) { preview.insert(Vector2i(x, y)); @@ -2921,7 +3027,7 @@ void TileMapEditorTerrainsPlugin::forward_canvas_draw_over_viewport(Control *p_o expand_grid = true; } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button && drag_type == DRAG_TYPE_NONE) { // Preview for a fill. - preview = _get_cells_for_bucket_fill(tile_map->world_to_map(drag_last_mouse_pos), bucket_contiguous_checkbox->is_pressed()); + preview = _get_cells_for_bucket_fill(tile_map->local_to_map(drag_last_mouse_pos), bucket_contiguous_checkbox->is_pressed()); } // Expand the grid if needed @@ -2937,9 +3043,9 @@ void TileMapEditorTerrainsPlugin::forward_canvas_draw_over_viewport(Control *p_o const int fading = 5; // Draw the lines of the grid behind the preview. - bool display_grid = EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid"); + bool display_grid = EDITOR_GET("editors/tiles_editor/display_grid"); if (display_grid) { - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); if (drawn_grid_rect.size.x > 0 && drawn_grid_rect.size.y > 0) { drawn_grid_rect = drawn_grid_rect.grow(fading); for (int x = drawn_grid_rect.position.x; x < (drawn_grid_rect.position.x + drawn_grid_rect.size.x); x++) { @@ -2948,13 +3054,13 @@ void TileMapEditorTerrainsPlugin::forward_canvas_draw_over_viewport(Control *p_o // Fade out the border of the grid. float left_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.x), 0.0f, 1.0f); - float right_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.x, (float)(drawn_grid_rect.size.x - fading), (float)pos_in_rect.x), 0.0f, 1.0f); + float right_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.x, (float)(drawn_grid_rect.size.x - fading), (float)(pos_in_rect.x + 1)), 0.0f, 1.0f); float top_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.y), 0.0f, 1.0f); - float bottom_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.y, (float)(drawn_grid_rect.size.y - fading), (float)pos_in_rect.y), 0.0f, 1.0f); + float bottom_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.y, (float)(drawn_grid_rect.size.y - fading), (float)(pos_in_rect.y + 1)), 0.0f, 1.0f); float opacity = CLAMP(MIN(left_opacity, MIN(right_opacity, MIN(top_opacity, bottom_opacity))) + 0.1, 0.0f, 1.0f); Transform2D tile_xform; - tile_xform.set_origin(tile_map->map_to_world(Vector2(x, y))); + tile_xform.set_origin(tile_map->map_to_local(Vector2(x, y))); tile_xform.set_scale(tile_shape_size); Color color = grid_color; color.a = color.a * opacity; @@ -2967,7 +3073,7 @@ void TileMapEditorTerrainsPlugin::forward_canvas_draw_over_viewport(Control *p_o // Draw the preview. for (const Vector2i &E : preview) { Transform2D tile_xform; - tile_xform.set_origin(tile_map->map_to_world(E)); + tile_xform.set_origin(tile_map->map_to_local(E)); tile_xform.set_scale(tile_set->get_tile_size()); if (drag_erasing || erase_button->is_pressed()) { tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(0.0, 0.0, 0.0, 0.5), true); @@ -2994,8 +3100,8 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() { per_terrain_terrains_patterns.resize(tile_set->get_terrain_sets_count()); for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) { per_terrain_terrains_patterns[i].resize(tile_set->get_terrains_count(i)); - for (int j = 0; j < (int)per_terrain_terrains_patterns[i].size(); j++) { - per_terrain_terrains_patterns[i][j].clear(); + for (RBSet<TileSet::TerrainsPattern> &pattern : per_terrain_terrains_patterns[i]) { + pattern.clear(); } } @@ -3010,7 +3116,7 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() { for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) { int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index); - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id)); + TileData *tile_data = atlas_source->get_tile_data(tile_id, alternative_id); int terrain_set = tile_data->get_terrain_set(); if (terrain_set >= 0) { ERR_FAIL_INDEX(terrain_set, (int)per_terrain_terrains_patterns.size()); @@ -3021,11 +3127,18 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() { cell.alternative_tile = alternative_id; TileSet::TerrainsPattern terrains_pattern = tile_data->get_terrains_pattern(); + + // Terrain center bit + int terrain = terrains_pattern.get_terrain(); + if (terrain >= 0 && terrain < (int)per_terrain_terrains_patterns[terrain_set].size()) { + per_terrain_terrains_patterns[terrain_set][terrain].insert(terrains_pattern); + } + // Terrain bits. for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - int terrain = terrains_pattern.get_terrain(bit); + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { + terrain = terrains_pattern.get_terrain_peering_bit(bit); if (terrain >= 0 && terrain < (int)per_terrain_terrains_patterns[terrain_set].size()) { per_terrain_terrains_patterns[terrain_set][terrain].insert(terrains_pattern); } @@ -3068,7 +3181,7 @@ void TileMapEditorTerrainsPlugin::_update_terrains_tree() { terrain_set_tree_item->set_icon(0, main_vbox_container->get_theme_icon(SNAME("TerrainMatchSides"), SNAME("EditorIcons"))); matches = String(TTR("Matches Sides Only")); } - terrain_set_tree_item->set_text(0, vformat("Terrain Set %d (%s)", terrain_set_index, matches)); + terrain_set_tree_item->set_text(0, vformat(TTR("Terrain Set %d (%s)"), terrain_set_index, matches)); terrain_set_tree_item->set_selectable(0, false); for (int terrain_index = 0; terrain_index < tile_set->get_terrains_count(terrain_set_index); terrain_index++) { @@ -3102,30 +3215,43 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() { TreeItem *selected_tree_item = terrains_tree->get_selected(); if (selected_tree_item && selected_tree_item->get_metadata(0)) { Dictionary metadata_dict = selected_tree_item->get_metadata(0); - int selected_terrain_set = metadata_dict["terrain_set"]; - int selected_terrain_id = metadata_dict["terrain_id"]; - ERR_FAIL_INDEX(selected_terrain_set, tile_set->get_terrain_sets_count()); - ERR_FAIL_INDEX(selected_terrain_id, tile_set->get_terrains_count(selected_terrain_set)); + int sel_terrain_set = metadata_dict["terrain_set"]; + int sel_terrain_id = metadata_dict["terrain_id"]; + ERR_FAIL_INDEX(sel_terrain_set, tile_set->get_terrain_sets_count()); + ERR_FAIL_INDEX(sel_terrain_id, tile_set->get_terrains_count(sel_terrain_set)); + + // Add the two first generic modes + int item_index = terrains_tile_list->add_icon_item(main_vbox_container->get_theme_icon(SNAME("TerrainConnect"), SNAME("EditorIcons"))); + terrains_tile_list->set_item_tooltip(item_index, TTR("Connect mode: paints a terrain, then connects it with the surrounding tiles with the same terrain.")); + Dictionary list_metadata_dict; + list_metadata_dict["type"] = SELECTED_TYPE_CONNECT; + terrains_tile_list->set_item_metadata(item_index, list_metadata_dict); + + item_index = terrains_tile_list->add_icon_item(main_vbox_container->get_theme_icon(SNAME("TerrainPath"), SNAME("EditorIcons"))); + terrains_tile_list->set_item_tooltip(item_index, TTR("Path mode: paints a terrain, thens connects it to the previous tile painted within the same stroke.")); + list_metadata_dict = Dictionary(); + list_metadata_dict["type"] = SELECTED_TYPE_PATH; + terrains_tile_list->set_item_metadata(item_index, list_metadata_dict); // Sort the items in a map by the number of corresponding terrains. - Map<int, Set<TileSet::TerrainsPattern>> sorted; + RBMap<int, RBSet<TileSet::TerrainsPattern>> sorted; - for (Set<TileSet::TerrainsPattern>::Element *E = per_terrain_terrains_patterns[selected_terrain_set][selected_terrain_id].front(); E; E = E->next()) { + for (const TileSet::TerrainsPattern &E : per_terrain_terrains_patterns[sel_terrain_set][sel_terrain_id]) { // Count the number of matching sides/terrains. int count = 0; for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(selected_terrain_set, bit) && E->get().get_terrain(bit) == selected_terrain_id) { + if (tile_set->is_valid_terrain_peering_bit(sel_terrain_set, bit) && E.get_terrain_peering_bit(bit) == sel_terrain_id) { count++; } } - sorted[count].insert(E->get()); + sorted[count].insert(E); } - for (Map<int, Set<TileSet::TerrainsPattern>>::Element *E_set = sorted.back(); E_set; E_set = E_set->prev()) { - for (Set<TileSet::TerrainsPattern>::Element *E = E_set->get().front(); E; E = E->next()) { - TileSet::TerrainsPattern terrains_pattern = E->get(); + for (RBMap<int, RBSet<TileSet::TerrainsPattern>>::Element *E_set = sorted.back(); E_set; E_set = E_set->prev()) { + for (const TileSet::TerrainsPattern &E : E_set->get()) { + TileSet::TerrainsPattern terrains_pattern = E; // Get the icon. Ref<Texture2D> icon; @@ -3133,12 +3259,12 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() { bool transpose = false; double max_probability = -1.0; - for (const TileMapCell &cell : tile_set->get_tiles_for_terrains_pattern(selected_terrain_set, terrains_pattern)) { + for (const TileMapCell &cell : tile_set->get_tiles_for_terrains_pattern(sel_terrain_set, terrains_pattern)) { Ref<TileSetSource> source = tile_set->get_source(cell.source_id); Ref<TileSetAtlasSource> atlas_source = source; if (atlas_source.is_valid()) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile)); + TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); if (tile_data->get_probability() > max_probability) { icon = atlas_source->get_texture(); region = atlas_source->get_tile_texture_region(cell.get_atlas_coords()); @@ -3157,12 +3283,13 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() { } // Create the ItemList's item. - int item_index = terrains_tile_list->add_item(""); + item_index = terrains_tile_list->add_item(""); terrains_tile_list->set_item_icon(item_index, icon); terrains_tile_list->set_item_icon_region(item_index, region); terrains_tile_list->set_item_icon_transposed(item_index, transpose); - Dictionary list_metadata_dict; - list_metadata_dict["terrains_pattern"] = terrains_pattern.get_terrains_as_array(); + list_metadata_dict = Dictionary(); + list_metadata_dict["type"] = SELECTED_TYPE_PATTERN; + list_metadata_dict["terrains_pattern"] = terrains_pattern.as_array(); terrains_tile_list->set_item_metadata(item_index, list_metadata_dict); } } @@ -3174,30 +3301,36 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() { void TileMapEditorTerrainsPlugin::_update_theme() { paint_tool_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); - line_tool_button->set_icon(main_vbox_container->get_theme_icon(SNAME("CurveLinear"), SNAME("EditorIcons"))); + line_tool_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Line"), SNAME("EditorIcons"))); rect_tool_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Rectangle"), SNAME("EditorIcons"))); bucket_tool_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Bucket"), SNAME("EditorIcons"))); picker_button->set_icon(main_vbox_container->get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); erase_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons"))); + + _update_tiles_list(); } void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer) { _stop_dragging(); // Avoids staying in a wrong drag state. - tile_map_id = p_tile_map_id; - tile_map_layer = p_tile_map_layer; + if (tile_map_id != p_tile_map_id) { + tile_map_id = p_tile_map_id; - _update_terrains_cache(); - _update_terrains_tree(); - _update_tiles_list(); + // Clear the selection. + _update_terrains_cache(); + _update_terrains_tree(); + _update_tiles_list(); + } + + tile_map_layer = p_tile_map_layer; } TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { main_vbox_container = memnew(VBoxContainer); main_vbox_container->connect("tree_entered", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_theme)); main_vbox_container->connect("theme_changed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_theme)); - main_vbox_container->set_name("Terrains"); + main_vbox_container->set_name(TTR("Terrains")); HSplitContainer *tilemap_tab_terrains = memnew(HSplitContainer); tilemap_tab_terrains->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -3207,7 +3340,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { terrains_tree = memnew(Tree); terrains_tree->set_h_size_flags(Control::SIZE_EXPAND_FILL); terrains_tree->set_stretch_ratio(0.25); - terrains_tree->set_custom_minimum_size(Size2i(70, 0) * EDSCALE); + terrains_tree->set_custom_minimum_size(Size2(70, 0) * EDSCALE); terrains_tree->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); terrains_tree->set_hide_root(true); terrains_tree->connect("item_selected", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_tiles_list)); @@ -3217,7 +3350,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { terrains_tile_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); terrains_tile_list->set_max_columns(0); terrains_tile_list->set_same_column_width(true); - terrains_tile_list->set_fixed_icon_size(Size2(30, 30) * EDSCALE); + terrains_tile_list->set_fixed_icon_size(Size2(32, 32) * EDSCALE); terrains_tile_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); tilemap_tab_terrains->add_child(terrains_tile_list); @@ -3233,7 +3366,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { paint_tool_button->set_toggle_mode(true); paint_tool_button->set_button_group(tool_buttons_group); paint_tool_button->set_pressed(true); - paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", Key::D)); + paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", TTR("Paint"), Key::D)); paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(paint_tool_button); @@ -3241,7 +3374,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { line_tool_button->set_flat(true); line_tool_button->set_toggle_mode(true); line_tool_button->set_button_group(tool_buttons_group); - line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", "Line", Key::L)); + line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", TTR("Line"), Key::L)); line_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(line_tool_button); @@ -3249,7 +3382,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { rect_tool_button->set_flat(true); rect_tool_button->set_toggle_mode(true); rect_tool_button->set_button_group(tool_buttons_group); - rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", "Rect", Key::R)); + rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", TTR("Rect"), Key::R)); rect_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(rect_tool_button); @@ -3257,7 +3390,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { bucket_tool_button->set_flat(true); bucket_tool_button->set_toggle_mode(true); bucket_tool_button->set_button_group(tool_buttons_group); - bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", "Bucket", Key::B)); + bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", TTR("Bucket"), Key::B)); bucket_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(bucket_tool_button); @@ -3274,7 +3407,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { picker_button = memnew(Button); picker_button->set_flat(true); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", Key::P)); + picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P)); picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(picker_button); @@ -3282,7 +3415,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { erase_button = memnew(Button); erase_button->set_flat(true); erase_button->set_toggle_mode(true); - erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", Key::E)); + erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", TTR("Eraser"), Key::E)); erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(erase_button); @@ -3304,15 +3437,16 @@ TileMapEditorTerrainsPlugin::~TileMapEditorTerrainsPlugin() { void TileMapEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { missing_tile_texture = get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); warning_pattern_texture = get_theme_icon(SNAME("WarningPattern"), SNAME("EditorIcons")); advanced_menu_button->set_icon(get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); toggle_grid_button->set_icon(get_theme_icon(SNAME("Grid"), SNAME("EditorIcons"))); - toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); - toogle_highlight_selected_layer_button->set_icon(get_theme_icon(SNAME("TileMapHighlightSelected"), SNAME("EditorIcons"))); - break; - case NOTIFICATION_INTERNAL_PROCESS: + toggle_grid_button->set_pressed(EDITOR_GET("editors/tiles_editor/display_grid")); + toggle_highlight_selected_layer_button->set_icon(get_theme_icon(SNAME("TileMapHighlightSelected"), SNAME("EditorIcons"))); + } break; + + case NOTIFICATION_INTERNAL_PROCESS: { if (is_visible_in_tree() && tileset_changed_needs_update) { _update_bottom_panel(); _update_layers_selection(); @@ -3320,11 +3454,13 @@ void TileMapEditor::_notification(int p_what) { CanvasItemEditor::get_singleton()->update_viewport(); tileset_changed_needs_update = false; } - break; - case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: - toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); - break; - case NOTIFICATION_VISIBILITY_CHANGED: + } break; + + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + toggle_grid_button->set_pressed(EDITOR_GET("editors/tiles_editor/display_grid")); + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (tile_map) { if (is_visible_in_tree()) { @@ -3333,72 +3469,22 @@ void TileMapEditor::_notification(int p_what) { tile_map->set_selected_layer(-1); } } - break; + } break; } } void TileMapEditor::_on_grid_toggled(bool p_pressed) { EditorSettings::get_singleton()->set("editors/tiles_editor/display_grid", p_pressed); + CanvasItemEditor::get_singleton()->update_viewport(); } -void TileMapEditor::_layers_selection_button_draw() { - if (!has_theme_icon(SNAME("arrow"), SNAME("OptionButton"))) { +void TileMapEditor::_layers_selection_item_selected(int p_index) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map || tile_map->get_layers_count() <= 0) { return; } - RID ci = layers_selection_button->get_canvas_item(); - Ref<Texture2D> arrow = Control::get_theme_icon(SNAME("arrow"), SNAME("OptionButton")); - - Color clr = Color(1, 1, 1); - if (get_theme_constant(SNAME("modulate_arrow"))) { - switch (layers_selection_button->get_draw_mode()) { - case BaseButton::DRAW_PRESSED: - clr = get_theme_color(SNAME("font_pressed_color")); - break; - case BaseButton::DRAW_HOVER: - clr = get_theme_color(SNAME("font_hover_color")); - break; - case BaseButton::DRAW_DISABLED: - clr = get_theme_color(SNAME("font_disabled_color")); - break; - default: - if (layers_selection_button->has_focus()) { - clr = get_theme_color(SNAME("font_focus_color")); - } else { - clr = get_theme_color(SNAME("font_color")); - } - } - } - - Size2 size = layers_selection_button->get_size(); - - Point2 ofs; - if (is_layout_rtl()) { - ofs = Point2(get_theme_constant(SNAME("arrow_margin"), SNAME("OptionButton")), int(Math::abs((size.height - arrow->get_height()) / 2))); - } else { - ofs = Point2(size.width - arrow->get_width() - get_theme_constant(SNAME("arrow_margin"), SNAME("OptionButton")), int(Math::abs((size.height - arrow->get_height()) / 2))); - } - Rect2 dst_rect = Rect2(ofs, arrow->get_size()); - if (!layers_selection_button->is_pressed()) { - dst_rect.size = -dst_rect.size; - } - arrow->draw_rect(ci, dst_rect, false, clr); -} - -void TileMapEditor::_layers_selection_button_pressed() { - if (!layers_selection_popup->is_visible()) { - Size2 size = layers_selection_popup->get_contents_minimum_size(); - size.x = MAX(size.x, layers_selection_button->get_size().x); - layers_selection_popup->set_position(layers_selection_button->get_screen_position() - Size2(0, size.y * get_global_transform().get_scale().y)); - layers_selection_popup->set_size(size); - layers_selection_popup->popup(); - } else { - layers_selection_popup->hide(); - } -} - -void TileMapEditor::_layers_selection_id_pressed(int p_id) { - tile_map_layer = p_id; + tile_map_layer = p_index; _update_layers_selection(); } @@ -3414,6 +3500,7 @@ void TileMapEditor::_advanced_menu_button_id_pressed(int p_id) { } if (p_id == 0) { // Replace Tile Proxies + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Replace Tiles with Proxies")); for (int layer_index = 0; layer_index < tile_map->get_layers_count(); layer_index++) { TypedArray<Vector2i> used_cells = tile_map->get_used_cells(layer_index); @@ -3444,8 +3531,8 @@ void TileMapEditor::_update_bottom_panel() { // Update the visibility of controls. missing_tileset_label->set_visible(!tile_set.is_valid()); - for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) { - tabs_data[tab_index].panel->hide(); + for (TileMapEditorPlugin::TabData &tab_data : tabs_data) { + tab_data.panel->hide(); } if (tile_set.is_valid()) { tabs_data[tabs_bar->get_current_tab()].panel->show(); @@ -3534,22 +3621,22 @@ void TileMapEditor::_tab_changed(int p_tab_id) { tabs_plugins[tabs_bar->get_current_tab()]->edit(tile_map_id, tile_map_layer); // Update toolbar. - for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) { - tabs_data[tab_index].toolbar->hide(); + for (TileMapEditorPlugin::TabData &tab_data : tabs_data) { + tab_data.toolbar->hide(); } tabs_data[p_tab_id].toolbar->show(); // Update visible panel. TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); - for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) { - tabs_data[tab_index].panel->hide(); + for (TileMapEditorPlugin::TabData &tab_data : tabs_data) { + tab_data.panel->hide(); } if (tile_map && tile_map->get_tileset().is_valid()) { tabs_data[tabs_bar->get_current_tab()].panel->show(); } // Graphical update. - tabs_data[tabs_bar->get_current_tab()].panel->update(); + tabs_data[tabs_bar->get_current_tab()].panel->queue_redraw(); CanvasItemEditor::get_singleton()->update_viewport(); } @@ -3581,8 +3668,6 @@ void TileMapEditor::_layers_select_next_or_previous(bool p_next) { } void TileMapEditor::_update_layers_selection() { - layers_selection_popup->clear(); - TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { return; @@ -3609,39 +3694,31 @@ void TileMapEditor::_update_layers_selection() { } else { tile_map_layer = -1; } - tile_map->set_selected_layer(toogle_highlight_selected_layer_button->is_pressed() ? tile_map_layer : -1); + tile_map->set_selected_layer(toggle_highlight_selected_layer_button->is_pressed() ? tile_map_layer : -1); + tileset_changed_needs_update = false; // Update is not needed here and actually causes problems. - // Build the list of layers. - for (int i = 0; i < tile_map->get_layers_count(); i++) { - String name = tile_map->get_layer_name(i); - layers_selection_popup->add_item(name.is_empty() ? vformat(TTR("Layer #%d"), i) : name, i); - layers_selection_popup->set_item_as_radio_checkable(i, true); - layers_selection_popup->set_item_disabled(i, !tile_map->is_layer_enabled(i)); - layers_selection_popup->set_item_checked(i, i == tile_map_layer); - } + layers_selection_button->clear(); + if (tile_map->get_layers_count() > 0) { + // Build the list of layers. + for (int i = 0; i < tile_map->get_layers_count(); i++) { + String name = tile_map->get_layer_name(i); + layers_selection_button->add_item(name.is_empty() ? vformat(TTR("Layer %d"), i) : name, i); + layers_selection_button->set_item_disabled(i, !tile_map->is_layer_enabled(i)); + } - // Update the button label. - if (tile_map_layer >= 0) { - layers_selection_button->set_text(layers_selection_popup->get_item_text(tile_map_layer)); + layers_selection_button->set_disabled(false); + layers_selection_button->select(tile_map_layer); } else { - layers_selection_button->set_text(TTR("Select a layer")); + layers_selection_button->set_disabled(true); + layers_selection_button->set_text(TTR("No Layers")); } - // Set button minimum width. - Size2 min_button_size = Size2(layers_selection_popup->get_contents_minimum_size().x, 0); - if (has_theme_icon(SNAME("arrow"), SNAME("OptionButton"))) { - Ref<Texture2D> arrow = Control::get_theme_icon(SNAME("arrow"), SNAME("OptionButton")); - min_button_size.x += arrow->get_size().x; - } - layers_selection_button->set_custom_minimum_size(min_button_size); - layers_selection_button->update(); - tabs_plugins[tabs_bar->get_current_tab()]->edit(tile_map_id, tile_map_layer); } void TileMapEditor::_move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) { - UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); - ERR_FAIL_COND(!undo_redo); + EditorUndoRedoManager *undo_redo_man = Object::cast_to<EditorUndoRedoManager>(p_undo_redo); + ERR_FAIL_NULL(undo_redo_man); TileMap *tile_map = Object::cast_to<TileMap>(p_edited); if (!tile_map) { @@ -3672,12 +3749,12 @@ void TileMapEditor::_move_tile_map_array_element(Object *p_undo_redo, Object *p_ end = MIN(MAX(p_from_index, p_to_pos) + 1, end); } -#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); +#define ADD_UNDO(obj, property) undo_redo_man->add_undo_property(obj, property, obj->get(property)); // Save layers' properties. if (p_from_index < 0) { - undo_redo->add_undo_method(tile_map, "remove_layer", p_to_pos < 0 ? tile_map->get_layers_count() : p_to_pos); + undo_redo_man->add_undo_method(tile_map, "remove_layer", p_to_pos < 0 ? tile_map->get_layers_count() : p_to_pos); } else if (p_to_pos < 0) { - undo_redo->add_undo_method(tile_map, "add_layer", p_from_index); + undo_redo_man->add_undo_method(tile_map, "add_layer", p_from_index); } List<PropertyInfo> properties; @@ -3687,7 +3764,7 @@ void TileMapEditor::_move_tile_map_array_element(Object *p_undo_redo, Object *p_ String str = pi.name.trim_prefix(p_array_prefix); int to_char_index = 0; while (to_char_index < str.length()) { - if (str[to_char_index] < '0' || str[to_char_index] > '9') { + if (!is_digit(str[to_char_index])) { break; } to_char_index++; @@ -3703,11 +3780,11 @@ void TileMapEditor::_move_tile_map_array_element(Object *p_undo_redo, Object *p_ #undef ADD_UNDO if (p_from_index < 0) { - undo_redo->add_do_method(tile_map, "add_layer", p_to_pos); + undo_redo_man->add_do_method(tile_map, "add_layer", p_to_pos); } else if (p_to_pos < 0) { - undo_redo->add_do_method(tile_map, "remove_layer", p_from_index); + undo_redo_man->add_do_method(tile_map, "remove_layer", p_from_index); } else { - undo_redo->add_do_method(tile_map, "move_layer", p_from_index, p_to_pos); + undo_redo_man->add_do_method(tile_map, "move_layer", p_from_index, p_to_pos); } } @@ -3780,7 +3857,7 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { // Draw the scaled tile. Transform2D tile_xform; - tile_xform.set_origin(tile_map->map_to_world(coords)); + tile_xform.set_origin(tile_map->map_to_local(coords)); tile_xform.set_scale(tile_shape_size); tile_set->draw_tile_shape(p_overlay, xform * tile_xform, color, true, warning_pattern_texture); } @@ -3790,7 +3867,7 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { Vector2 icon_size; icon_size[min_axis] = tile_set->get_tile_size()[min_axis] / 3; icon_size[(min_axis + 1) % 2] = (icon_size[min_axis] * missing_tile_texture->get_size()[(min_axis + 1) % 2] / missing_tile_texture->get_size()[min_axis]); - Rect2 rect = Rect2(xform.xform(tile_map->map_to_world(coords)) - (icon_size * xform.get_scale() / 2), icon_size * xform.get_scale()); + Rect2 rect = Rect2(xform.xform(tile_map->map_to_local(coords)) - (icon_size * xform.get_scale() / 2), icon_size * xform.get_scale()); p_overlay->draw_texture_rect(missing_tile_texture, rect); } } @@ -3803,10 +3880,10 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { // Determine the drawn area. Size2 screen_size = p_overlay->get_size(); Rect2i screen_rect; - screen_rect.position = tile_map->world_to_map(xform_inv.xform(Vector2())); - screen_rect.expand_to(tile_map->world_to_map(xform_inv.xform(Vector2(0, screen_size.height)))); - screen_rect.expand_to(tile_map->world_to_map(xform_inv.xform(Vector2(screen_size.width, 0)))); - screen_rect.expand_to(tile_map->world_to_map(xform_inv.xform(screen_size))); + screen_rect.position = tile_map->local_to_map(xform_inv.xform(Vector2())); + screen_rect.expand_to(tile_map->local_to_map(xform_inv.xform(Vector2(0, screen_size.height)))); + screen_rect.expand_to(tile_map->local_to_map(xform_inv.xform(Vector2(screen_size.width, 0)))); + screen_rect.expand_to(tile_map->local_to_map(xform_inv.xform(screen_size))); screen_rect = screen_rect.grow(1); Rect2i tilemap_used_rect = tile_map->get_used_rect(); @@ -3824,22 +3901,22 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { } // Draw the grid. - bool display_grid = EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid"); + bool display_grid = EDITOR_GET("editors/tiles_editor/display_grid"); if (display_grid) { - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); for (int x = displayed_rect.position.x; x < (displayed_rect.position.x + displayed_rect.size.x); x++) { for (int y = displayed_rect.position.y; y < (displayed_rect.position.y + displayed_rect.size.y); y++) { Vector2i pos_in_rect = Vector2i(x, y) - displayed_rect.position; // Fade out the border of the grid. float left_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.x), 0.0f, 1.0f); - float right_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.x, (float)(displayed_rect.size.x - fading), (float)pos_in_rect.x), 0.0f, 1.0f); + float right_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.x, (float)(displayed_rect.size.x - fading), (float)(pos_in_rect.x + 1)), 0.0f, 1.0f); float top_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.y), 0.0f, 1.0f); - float bottom_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.y, (float)(displayed_rect.size.y - fading), (float)pos_in_rect.y), 0.0f, 1.0f); + float bottom_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.y, (float)(displayed_rect.size.y - fading), (float)(pos_in_rect.y + 1)), 0.0f, 1.0f); float opacity = CLAMP(MIN(left_opacity, MIN(right_opacity, MIN(top_opacity, bottom_opacity))) + 0.1, 0.0f, 1.0f); Transform2D tile_xform; - tile_xform.set_origin(tile_map->map_to_world(Vector2(x, y))); + tile_xform.set_origin(tile_map->map_to_local(Vector2(x, y))); tile_xform.set_scale(tile_shape_size); Color color = grid_color; color.a = color.a * opacity; @@ -3852,7 +3929,7 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { /*Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); for (int x = displayed_rect.position.x; x < (displayed_rect.position.x + displayed_rect.size.x); x++) { for (int y = displayed_rect.position.y; y < (displayed_rect.position.y + displayed_rect.size.y); y++) { - p_overlay->draw_string(font, xform.xform(tile_map->map_to_world(Vector2(x, y))) + Vector2i(-tile_shape_size.x / 2, 0), vformat("%s", Vector2(x, y))); + p_overlay->draw_string(font, xform.xform(tile_map->map_to_local(Vector2(x, y))) + Vector2i(-tile_shape_size.x / 2, 0), vformat("%s", Vector2(x, y))); } }*/ @@ -3921,7 +3998,7 @@ TileMapEditor::TileMapEditor() { tabs_bar->connect("tab_changed", callable_mp(this, &TileMapEditor::_tab_changed)); // --- TileMap toolbar --- - tile_map_toolbar = memnew(HBoxContainer); + tile_map_toolbar = memnew(HFlowContainer); tile_map_toolbar->set_h_size_flags(SIZE_EXPAND_FILL); add_child(tile_map_toolbar); @@ -3929,39 +4006,34 @@ TileMapEditor::TileMapEditor() { tile_map_toolbar->add_child(tabs_bar); // Tabs toolbars. - for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) { - tabs_data[tab_index].toolbar->hide(); - if (!tabs_data[tab_index].toolbar->get_parent()) { - tile_map_toolbar->add_child(tabs_data[tab_index].toolbar); + for (TileMapEditorPlugin::TabData &tab_data : tabs_data) { + tab_data.toolbar->hide(); + if (!tab_data.toolbar->get_parent()) { + tile_map_toolbar->add_child(tab_data.toolbar); } } - // Wide empty separation control. - Control *h_empty_space = memnew(Control); - h_empty_space->set_h_size_flags(SIZE_EXPAND_FILL); - tile_map_toolbar->add_child(h_empty_space); + // Wide empty separation control. (like BoxContainer::add_spacer()) + Control *c = memnew(Control); + c->set_mouse_filter(MOUSE_FILTER_PASS); + c->set_h_size_flags(SIZE_EXPAND_FILL); + tile_map_toolbar->add_child(c); // Layer selector. - layers_selection_popup = memnew(PopupMenu); - layers_selection_popup->connect("id_pressed", callable_mp(this, &TileMapEditor::_layers_selection_id_pressed)); - layers_selection_popup->set_close_on_parent_focus(false); - - layers_selection_button = memnew(Button); - layers_selection_button->set_toggle_mode(true); - layers_selection_button->connect("draw", callable_mp(this, &TileMapEditor::_layers_selection_button_draw)); - layers_selection_button->connect("pressed", callable_mp(this, &TileMapEditor::_layers_selection_button_pressed)); - layers_selection_button->connect("hidden", callable_mp((Window *)layers_selection_popup, &Popup::hide)); - layers_selection_button->set_tooltip(TTR("Tile Map Layer")); - layers_selection_button->add_child(layers_selection_popup); + layers_selection_button = memnew(OptionButton); + layers_selection_button->set_custom_minimum_size(Size2(200, 0)); + layers_selection_button->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + layers_selection_button->set_tooltip_text(TTR("TileMap Layers")); + layers_selection_button->connect("item_selected", callable_mp(this, &TileMapEditor::_layers_selection_item_selected)); tile_map_toolbar->add_child(layers_selection_button); - toogle_highlight_selected_layer_button = memnew(Button); - toogle_highlight_selected_layer_button->set_flat(true); - toogle_highlight_selected_layer_button->set_toggle_mode(true); - toogle_highlight_selected_layer_button->set_pressed(true); - toogle_highlight_selected_layer_button->connect("pressed", callable_mp(this, &TileMapEditor::_update_layers_selection)); - toogle_highlight_selected_layer_button->set_tooltip(TTR("Highlight Selected TileMap Layer")); - tile_map_toolbar->add_child(toogle_highlight_selected_layer_button); + toggle_highlight_selected_layer_button = memnew(Button); + toggle_highlight_selected_layer_button->set_flat(true); + toggle_highlight_selected_layer_button->set_toggle_mode(true); + toggle_highlight_selected_layer_button->set_pressed(true); + toggle_highlight_selected_layer_button->connect("pressed", callable_mp(this, &TileMapEditor::_update_layers_selection)); + toggle_highlight_selected_layer_button->set_tooltip_text(TTR("Highlight Selected TileMap Layer")); + tile_map_toolbar->add_child(toggle_highlight_selected_layer_button); tile_map_toolbar->add_child(memnew(VSeparator)); @@ -3969,7 +4041,7 @@ TileMapEditor::TileMapEditor() { toggle_grid_button = memnew(Button); toggle_grid_button->set_flat(true); toggle_grid_button->set_toggle_mode(true); - toggle_grid_button->set_tooltip(TTR("Toggle grid visibility.")); + toggle_grid_button->set_tooltip_text(TTR("Toggle grid visibility.")); toggle_grid_button->connect("toggled", callable_mp(this, &TileMapEditor::_on_grid_toggled)); tile_map_toolbar->add_child(toggle_grid_button); diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index b1bee03211..1cab1d1500 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* tile_map_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_map_editor.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 TILE_MAP_EDITOR_H #define TILE_MAP_EDITOR_H @@ -35,16 +35,24 @@ #include "core/os/thread.h" #include "core/typedefs.h" -#include "editor/editor_node.h" #include "scene/2d/tile_map.h" #include "scene/gui/box_container.h" +#include "scene/gui/check_box.h" +#include "scene/gui/flow_container.h" +#include "scene/gui/item_list.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/option_button.h" +#include "scene/gui/separator.h" +#include "scene/gui/spin_box.h" +#include "scene/gui/split_container.h" #include "scene/gui/tab_bar.h" +#include "scene/gui/tree.h" class TileMapEditorPlugin : public Object { public: struct TabData { - Control *toolbar; - Control *panel; + Control *toolbar = nullptr; + Control *panel = nullptr; }; virtual Vector<TabData> get_tabs() const { @@ -61,33 +69,34 @@ class TileMapEditorTilesPlugin : public TileMapEditorPlugin { GDCLASS(TileMapEditorTilesPlugin, TileMapEditorPlugin); private: - UndoRedo *undo_redo = EditorNode::get_undo_redo(); ObjectID tile_map_id; int tile_map_layer = -1; virtual void edit(ObjectID p_tile_map_id, int p_tile_map_layer) override; ///// Toolbar ///// - HBoxContainer *toolbar; + HBoxContainer *toolbar = nullptr; Ref<ButtonGroup> tool_buttons_group; - Button *select_tool_button; - Button *paint_tool_button; - Button *line_tool_button; - Button *rect_tool_button; - Button *bucket_tool_button; + Button *select_tool_button = nullptr; + Button *paint_tool_button = nullptr; + Button *line_tool_button = nullptr; + Button *rect_tool_button = nullptr; + Button *bucket_tool_button = nullptr; - HBoxContainer *tools_settings; + HBoxContainer *tools_settings = nullptr; - VSeparator *tools_settings_vsep; - Button *picker_button; - Button *erase_button; + VSeparator *tools_settings_vsep = nullptr; + Button *picker_button = nullptr; + Button *erase_button = nullptr; - VSeparator *tools_settings_vsep_2; - CheckBox *bucket_contiguous_checkbox; - CheckBox *random_tile_checkbox; + VSeparator *tools_settings_vsep_2 = nullptr; + CheckBox *bucket_contiguous_checkbox = nullptr; + Button *random_tile_toggle = nullptr; + + HBoxContainer *scatter_controls_container = nullptr; float scattering = 0.0; - Label *scatter_label; - SpinBox *scatter_spinbox; + Label *scatter_label = nullptr; + SpinBox *scatter_spinbox = nullptr; void _on_random_tile_checkbox_toggled(bool p_pressed); void _on_scattering_spinbox_changed(double p_value); @@ -112,22 +121,22 @@ private: bool drag_erasing = false; Vector2 drag_start_mouse_pos; Vector2 drag_last_mouse_pos; - Map<Vector2i, TileMapCell> drag_modified; + HashMap<Vector2i, TileMapCell> drag_modified; TileMapCell _pick_random_tile(Ref<TileMapPattern> p_pattern); - Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos, bool p_erase); - Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase); - Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase); + HashMap<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos, bool p_erase); + HashMap<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase); + HashMap<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase); void _stop_dragging(); ///// Selection system. ///// - Set<Vector2i> tile_map_selection; + RBSet<Vector2i> tile_map_selection; Ref<TileMapPattern> tile_map_clipboard; Ref<TileMapPattern> selection_pattern; void _set_tile_map_selection(const TypedArray<Vector2i> &p_selection); TypedArray<Vector2i> _get_tile_map_selection() const; - Set<TileMapCell> tile_set_selection; + RBSet<TileMapCell> tile_set_selection; void _update_selection_pattern_from_tilemap_selection(); void _update_selection_pattern_from_tileset_tiles_selection(); @@ -136,15 +145,18 @@ private: void _update_fix_selected_and_hovered(); void _fix_invalid_tiles_in_tile_map_selection(); + void patterns_item_list_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index); + ///// Bottom panel common //// void _tab_changed(); ///// Bottom panel tiles //// - VBoxContainer *tiles_bottom_panel; - Label *missing_source_label; - Label *invalid_source_label; + VBoxContainer *tiles_bottom_panel = nullptr; + Label *missing_source_label = nullptr; + Label *invalid_source_label = nullptr; - ItemList *sources_list; + ItemList *sources_list = nullptr; + MenuButton *source_sort_button = nullptr; Ref<Texture2D> missing_atlas_texture_icon; void _update_tile_set_sources_list(); @@ -153,36 +165,37 @@ private: // Atlas sources. TileMapCell hovered_tile; - TileAtlasView *tile_atlas_view; - HSplitContainer *atlas_sources_split_container; + TileAtlasView *tile_atlas_view = nullptr; + HSplitContainer *atlas_sources_split_container = nullptr; bool tile_set_dragging_selection = false; Vector2i tile_set_drag_start_mouse_pos; - Control *tile_atlas_control; + Control *tile_atlas_control = nullptr; void _tile_atlas_control_mouse_exited(); void _tile_atlas_control_gui_input(const Ref<InputEvent> &p_event); void _tile_atlas_control_draw(); - Control *alternative_tiles_control; + Control *alternative_tiles_control = nullptr; void _tile_alternatives_control_draw(); void _tile_alternatives_control_mouse_exited(); void _tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event); void _update_atlas_view(); + void _set_source_sort(int p_sort); // Scenes collection sources. - ItemList *scene_tiles_list; + ItemList *scene_tiles_list = nullptr; void _update_scenes_collection_view(); void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud); void _scenes_list_multi_selected(int p_index, bool p_selected); - void _scenes_list_nothing_selected(); + void _scenes_list_lmb_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index); ///// Bottom panel patterns //// - VBoxContainer *patterns_bottom_panel; - ItemList *patterns_item_list; - Label *patterns_help_label; + VBoxContainer *patterns_bottom_panel = nullptr; + ItemList *patterns_item_list = nullptr; + Label *patterns_help_label = nullptr; void _patterns_item_list_gui_input(const Ref<InputEvent> &p_event); void _pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture); bool select_last_pattern = false; @@ -210,32 +223,31 @@ class TileMapEditorTerrainsPlugin : public TileMapEditorPlugin { GDCLASS(TileMapEditorTerrainsPlugin, TileMapEditorPlugin); private: - UndoRedo *undo_redo = EditorNode::get_undo_redo(); ObjectID tile_map_id; int tile_map_layer = -1; virtual void edit(ObjectID p_tile_map_id, int p_tile_map_layer) override; // Toolbar. - HBoxContainer *toolbar; + HBoxContainer *toolbar = nullptr; Ref<ButtonGroup> tool_buttons_group; - Button *paint_tool_button; - Button *line_tool_button; - Button *rect_tool_button; - Button *bucket_tool_button; + Button *paint_tool_button = nullptr; + Button *line_tool_button = nullptr; + Button *rect_tool_button = nullptr; + Button *bucket_tool_button = nullptr; - HBoxContainer *tools_settings; + HBoxContainer *tools_settings = nullptr; - VSeparator *tools_settings_vsep; - Button *picker_button; - Button *erase_button; + VSeparator *tools_settings_vsep = nullptr; + Button *picker_button = nullptr; + Button *erase_button = nullptr; - VSeparator *tools_settings_vsep_2; - CheckBox *bucket_contiguous_checkbox; + VSeparator *tools_settings_vsep_2 = nullptr; + CheckBox *bucket_contiguous_checkbox = nullptr; void _update_toolbar(); // Main vbox. - VBoxContainer *main_vbox_container; + VBoxContainer *main_vbox_container = nullptr; // TileMap editing. bool has_mouse = false; @@ -253,26 +265,34 @@ private: bool drag_erasing = false; Vector2 drag_start_mouse_pos; Vector2 drag_last_mouse_pos; - Map<Vector2i, TileMapCell> drag_modified; + HashMap<Vector2i, TileMapCell> drag_modified; // Painting - Map<Vector2i, TileMapCell> _draw_terrains(const Map<Vector2i, TileSet::TerrainsPattern> &p_to_paint, int p_terrain_set) const; - Map<Vector2i, TileMapCell> _draw_line(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase); - Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase); - Set<Vector2i> _get_cells_for_bucket_fill(Vector2i p_coords, bool p_contiguous); - Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase); + HashMap<Vector2i, TileMapCell> _draw_terrain_path_or_connect(const Vector<Vector2i> &p_to_paint, int p_terrain_set, int p_terrain, bool p_connect) const; + HashMap<Vector2i, TileMapCell> _draw_terrain_pattern(const Vector<Vector2i> &p_to_paint, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; + HashMap<Vector2i, TileMapCell> _draw_line(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase); + HashMap<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase); + RBSet<Vector2i> _get_cells_for_bucket_fill(Vector2i p_coords, bool p_contiguous); + HashMap<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase); void _stop_dragging(); + enum SelectedType { + SELECTED_TYPE_CONNECT = 0, + SELECTED_TYPE_PATH, + SELECTED_TYPE_PATTERN, + }; + SelectedType selected_type; int selected_terrain_set = -1; + int selected_terrain = -1; TileSet::TerrainsPattern selected_terrains_pattern; void _update_selection(); // Bottom panel. - Tree *terrains_tree; - ItemList *terrains_tile_list; + Tree *terrains_tree = nullptr; + ItemList *terrains_tile_list = nullptr; // Cache. - LocalVector<LocalVector<Set<TileSet::TerrainsPattern>>> per_terrain_terrains_patterns; + LocalVector<LocalVector<RBSet<TileSet::TerrainsPattern>>> per_terrain_terrains_patterns; // Update functions. void _update_terrains_cache(); @@ -296,7 +316,6 @@ class TileMapEditor : public VBoxContainer { GDCLASS(TileMapEditor, VBoxContainer); private: - UndoRedo *undo_redo = EditorNode::get_undo_redo(); bool tileset_changed_needs_update = false; ObjectID tile_map_id; int tile_map_layer = -1; @@ -305,24 +324,21 @@ private: Vector<TileMapEditorPlugin *> tile_map_editor_plugins; // Toolbar. - HBoxContainer *tile_map_toolbar; + HFlowContainer *tile_map_toolbar = nullptr; - PopupMenu *layers_selection_popup; - Button *layers_selection_button; - Button *toogle_highlight_selected_layer_button; - void _layers_selection_button_draw(); - void _layers_selection_button_pressed(); - void _layers_selection_id_pressed(int p_id); + OptionButton *layers_selection_button = nullptr; + Button *toggle_highlight_selected_layer_button = nullptr; + void _layers_selection_item_selected(int p_index); - Button *toggle_grid_button; + Button *toggle_grid_button = nullptr; void _on_grid_toggled(bool p_pressed); - MenuButton *advanced_menu_button; + MenuButton *advanced_menu_button = nullptr; void _advanced_menu_button_id_pressed(int p_id); // Bottom panel. - Label *missing_tileset_label; - TabBar *tabs_bar; + Label *missing_tileset_label = nullptr; + TabBar *tabs_bar = nullptr; LocalVector<TileMapEditorPlugin::TabData> tabs_data; LocalVector<TileMapEditorPlugin *> tabs_plugins; void _update_bottom_panel(); @@ -359,4 +375,4 @@ public: static Vector<Vector2i> get_line(TileMap *p_tile_map, Vector2i p_from_cell, Vector2i p_to_cell); }; -#endif // TILE_MAP_EDITOR_PLUGIN_H +#endif // TILE_MAP_EDITOR_H diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp index ad44da8dc9..f6aeffa13f 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp @@ -1,38 +1,47 @@ -/*************************************************************************/ -/* tile_proxies_manager_dialog.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_proxies_manager_dialog.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 "tile_proxies_manager_dialog.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/popup_menu.h" +#include "scene/gui/separator.h" + +void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index, Object *p_item_list) { + if (p_mouse_button_index != MouseButton::RIGHT) { + return; + } -void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list) { ItemList *item_list = Object::cast_to<ItemList>(p_item_list); popup_menu->reset_size(); popup_menu->set_position(get_position() + item_list->get_global_mouse_position()); @@ -47,6 +56,7 @@ void TileProxiesManagerDialog::_menu_id_pressed(int p_id) { } void TileProxiesManagerDialog::_delete_selected_bindings() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove Tile Proxies")); Vector<int> source_level_selected = source_level_list->get_selected_items(); @@ -68,7 +78,7 @@ void TileProxiesManagerDialog::_delete_selected_bindings() { Vector<int> alternative_level_selected = alternative_level_list->get_selected_items(); for (int i = 0; i < alternative_level_selected.size(); i++) { Array key = alternative_level_list->get_item_metadata(alternative_level_selected[i]); - Array val = tile_set->get_coords_level_tile_proxy(key[0], key[1]); + Array val = tile_set->get_alternative_level_tile_proxy(key[0], key[1], key[2]); undo_redo->add_do_method(*tile_set, "remove_alternative_level_tile_proxy", key[0], key[1], key[2]); undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", key[0], key[1], key[2], val[0], val[1], val[2]); } @@ -146,6 +156,7 @@ void TileProxiesManagerDialog::_property_changed(const String &p_path, const Var } void TileProxiesManagerDialog::_add_button_pressed() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (from.source_id != TileSet::INVALID_SOURCE && to.source_id != TileSet::INVALID_SOURCE) { Vector2i from_coords = from.get_atlas_coords(); Vector2i to_coords = to.get_atlas_coords(); @@ -186,6 +197,7 @@ void TileProxiesManagerDialog::_add_button_pressed() { } void TileProxiesManagerDialog::_clear_invalid_button_pressed() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Delete All Invalid Tile Proxies")); undo_redo->add_do_method(*tile_set, "cleanup_invalid_tile_proxies"); @@ -213,6 +225,7 @@ void TileProxiesManagerDialog::_clear_invalid_button_pressed() { } void TileProxiesManagerDialog::_clear_all_button_pressed() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Delete All Tile Proxies")); undo_redo->add_do_method(*tile_set, "clear_tile_proxies"); @@ -293,6 +306,7 @@ void TileProxiesManagerDialog::_unhandled_key_input(Ref<InputEvent> p_event) { } void TileProxiesManagerDialog::cancel_pressed() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); for (int i = 0; i < commited_actions_count; i++) { undo_redo->undo(); } @@ -333,7 +347,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() { source_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); source_level_list->set_select_mode(ItemList::SELECT_MULTI); source_level_list->set_allow_rmb_select(true); - source_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(source_level_list)); + source_level_list->connect("item_clicked", callable_mp(this, &TileProxiesManagerDialog::_right_clicked).bind(source_level_list)); vbox_container->add_child(source_level_list); Label *coords_level_label = memnew(Label); @@ -344,7 +358,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() { coords_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); coords_level_list->set_select_mode(ItemList::SELECT_MULTI); coords_level_list->set_allow_rmb_select(true); - coords_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(coords_level_list)); + coords_level_list->connect("item_clicked", callable_mp(this, &TileProxiesManagerDialog::_right_clicked).bind(coords_level_list)); vbox_container->add_child(coords_level_list); Label *alternative_level_label = memnew(Label); @@ -355,7 +369,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() { alternative_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); alternative_level_list->set_select_mode(ItemList::SELECT_MULTI); alternative_level_list->set_allow_rmb_select(true); - alternative_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(alternative_level_list)); + alternative_level_list->connect("item_clicked", callable_mp(this, &TileProxiesManagerDialog::_right_clicked).bind(alternative_level_list)); vbox_container->add_child(alternative_level_list); popup_menu = memnew(PopupMenu); @@ -385,7 +399,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() { source_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); source_from_property_editor->set_selectable(false); source_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - source_from_property_editor->setup(-1, 99999, 1, true, false); + source_from_property_editor->setup(-1, 99999, 1, false, true, false); vboxcontainer_from->add_child(source_from_property_editor); coords_from_property_editor = memnew(EditorPropertyVector2i); @@ -404,7 +418,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() { alternative_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); alternative_from_property_editor->set_selectable(false); alternative_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - alternative_from_property_editor->setup(-1, 99999, 1, true, false); + alternative_from_property_editor->setup(-1, 99999, 1, false, true, false); alternative_from_property_editor->hide(); vboxcontainer_from->add_child(alternative_from_property_editor); @@ -419,7 +433,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() { source_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); source_to_property_editor->set_selectable(false); source_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - source_to_property_editor->setup(-1, 99999, 1, true, false); + source_to_property_editor->setup(-1, 99999, 1, false, true, false); vboxcontainer_to->add_child(source_to_property_editor); coords_to_property_editor = memnew(EditorPropertyVector2i); @@ -438,7 +452,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() { alternative_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); alternative_to_property_editor->set_selectable(false); alternative_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - alternative_to_property_editor->setup(-1, 99999, 1, true, false); + alternative_to_property_editor->setup(-1, 99999, 1, false, true, false); alternative_to_property_editor->hide(); vboxcontainer_to->add_child(alternative_to_property_editor); diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.h b/editor/plugins/tiles/tile_proxies_manager_dialog.h index b235d44982..9fcdea8db9 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.h +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.h @@ -1,43 +1,43 @@ -/*************************************************************************/ -/* tile_proxies_manager_dialog.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_proxies_manager_dialog.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 TILE_PROXIES_MANAGER_DIALOG_H #define TILE_PROXIES_MANAGER_DIALOG_H -#include "editor/editor_node.h" #include "editor/editor_properties.h" - #include "scene/2d/tile_map.h" #include "scene/gui/dialogs.h" #include "scene/gui/item_list.h" +class EditorUndoRedoManager; + class TileProxiesManagerDialog : public ConfirmationDialog { GDCLASS(TileProxiesManagerDialog, ConfirmationDialog); @@ -45,25 +45,23 @@ private: int commited_actions_count = 0; Ref<TileSet> tile_set; - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); - TileMapCell from; TileMapCell to; // GUI - ItemList *source_level_list; - ItemList *coords_level_list; - ItemList *alternative_level_list; + ItemList *source_level_list = nullptr; + ItemList *coords_level_list = nullptr; + ItemList *alternative_level_list = nullptr; - EditorPropertyInteger *source_from_property_editor; - EditorPropertyVector2i *coords_from_property_editor; - EditorPropertyInteger *alternative_from_property_editor; - EditorPropertyInteger *source_to_property_editor; - EditorPropertyVector2i *coords_to_property_editor; - EditorPropertyInteger *alternative_to_property_editor; + EditorPropertyInteger *source_from_property_editor = nullptr; + EditorPropertyVector2i *coords_from_property_editor = nullptr; + EditorPropertyInteger *alternative_from_property_editor = nullptr; + EditorPropertyInteger *source_to_property_editor = nullptr; + EditorPropertyVector2i *coords_to_property_editor = nullptr; + EditorPropertyInteger *alternative_to_property_editor = nullptr; - PopupMenu *popup_menu; - void _right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list); + PopupMenu *popup_menu = nullptr; + void _right_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index, Object *p_item_list); void _menu_id_pressed(int p_id); void _delete_selected_bindings(); void _update_lists(); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index c4cc9745ee..fcefbb7d06 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -1,39 +1,43 @@ -/*************************************************************************/ -/* tile_set_atlas_source_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_set_atlas_source_editor.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 "tile_set_atlas_source_editor.h" #include "tiles_editor_plugin.h" #include "editor/editor_inspector.h" +#include "editor/editor_node.h" +#include "editor/editor_property_name_processor.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/progress_dialog.h" #include "scene/gui/box_container.h" @@ -61,11 +65,15 @@ void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::set_id(int p_id) { emit_signal(SNAME("changed"), "id"); } -int TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::get_id() { +int TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::get_id() const { return source_id; } bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "id") { + set_id(p_value); + return true; + } String name = p_name; if (name == "name") { // Use the resource_name property to store the source's name. @@ -83,6 +91,10 @@ bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get(const StringN if (!tile_set_atlas_source) { return false; } + if (p_name == "id") { + r_ret = get_id(); + return true; + } String name = p_name; if (name == "name") { // Use the resource_name property to store the source's name. @@ -94,29 +106,24 @@ bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get(const StringN } void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get_property_list(List<PropertyInfo> *p_list) const { - p_list->push_back(PropertyInfo(Variant::STRING, "name", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D")); - p_list->push_back(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::BOOL, "use_texture_padding", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::NIL, TTR("Atlas"), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("id"), PROPERTY_HINT_RANGE, "0," + itos(INT_MAX) + ",1")); + p_list->push_back(PropertyInfo(Variant::STRING, PNAME("name"))); + p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("texture"), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D")); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("margins"), PROPERTY_HINT_NONE, "suffix:px")); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("separation"))); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("texture_region_size"), PROPERTY_HINT_NONE, "suffix:px")); + p_list->push_back(PropertyInfo(Variant::BOOL, PNAME("use_texture_padding"))); } void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_bind_methods() { - // -- Shape and layout -- - ClassDB::bind_method(D_METHOD("set_id", "id"), &TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::set_id); - ClassDB::bind_method(D_METHOD("get_id"), &TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::get_id); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "id"), "set_id", "get_id"); - ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what"))); } void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) { - ERR_FAIL_COND(!p_tile_set.is_valid()); ERR_FAIL_COND(!p_tile_set_atlas_source); ERR_FAIL_COND(p_source_id < 0); - ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source); + ERR_FAIL_COND(p_tile_set.is_valid() && p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source); if (p_tile_set == tile_set && p_tile_set_atlas_source == tile_set_atlas_source && p_source_id == source_id) { return; @@ -149,7 +156,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na // ID and size related properties. if (tiles.size() == 1) { - const Vector2i &coords = tiles.front()->get().tile; + const Vector2i coords = tiles.front()->get().tile; const int &alternative = tiles.front()->get().alternative; if (alternative == 0) { @@ -157,7 +164,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na if (p_name == "atlas_coords") { Vector2i as_vector2i = Vector2i(p_value); bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(as_vector2i, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords); - ERR_FAIL_COND_V(!has_room_for_tile, false); + ERR_FAIL_COND_V_EDMSG(!has_room_for_tile, false, "Cannot move the tile, invalid coordinates or not enough room in the atlas for the tile and its animation frames."); if (tiles_set_atlas_source_editor->selection.front()->get().tile == coords) { tiles_set_atlas_source_editor->selection.clear(); @@ -216,7 +223,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na for (TileSelection tile : tiles) { bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_separation(tile.tile), tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile); if (!has_room_for_tile) { - ERR_PRINT("No room for tile"); + ERR_PRINT(vformat("Cannot change the number of columns to %s for tile animation. Not enough room in the atlas to layout %s frame(s).", p_value, tile_set_atlas_source->get_tile_animation_frames_count(tile.tile))); } else { tile_set_atlas_source->set_tile_animation_columns(tile.tile, p_value); } @@ -227,7 +234,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na for (TileSelection tile : tiles) { bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile); if (!has_room_for_tile) { - ERR_PRINT("No room for tile"); + ERR_PRINT(vformat("Cannot change separation between frames of the animation to %s. Not enough room in the atlas to layout %s frame(s).", p_value, tile_set_atlas_source->get_tile_animation_frames_count(tile.tile))); } else { tile_set_atlas_source->set_tile_animation_separation(tile.tile, p_value); } @@ -242,11 +249,16 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na return true; } else if (p_name == "animation_frames_count") { for (TileSelection tile : tiles) { - bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), tile_set_atlas_source->get_tile_animation_separation(tile.tile), p_value, tile.tile); + int frame_count = p_value; + if (frame_count == 0) { + frame_count = 1; + } + + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), tile_set_atlas_source->get_tile_animation_separation(tile.tile), frame_count, tile.tile); if (!has_room_for_tile) { - ERR_PRINT("No room for tile"); + ERR_PRINT(vformat("Cannot add frames to the animation, not enough room in the atlas to layout %s frames.", frame_count)); } else { - tile_set_atlas_source->set_tile_animation_frames_count(tile.tile, p_value); + tile_set_atlas_source->set_tile_animation_frames_count(tile.tile, frame_count); } } notify_property_list_changed(); @@ -269,12 +281,12 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na // Other properties. bool any_valid = false; - for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { - const Vector2i &coords = E->get().tile; - const int &alternative = E->get().alternative; + for (const TileSelection &E : tiles) { + const Vector2i &coords = E.tile; + const int &alternative = E.alternative; bool valid = false; - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative); ERR_FAIL_COND_V(!tile_data, false); tile_data->set(p_name, p_value, &valid); @@ -353,13 +365,13 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na } } - for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { + for (const TileSelection &E : tiles) { // Return the first tile with a property matching the name. // Note: It's a little bit annoying, but the behavior is the same the one in MultiNodeEdit. - const Vector2i &coords = E->get().tile; - const int &alternative = E->get().alternative; + const Vector2i &coords = E.tile; + const int &alternative = E.alternative; - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative); ERR_FAIL_COND_V(!tile_data, false); bool valid = false; @@ -380,11 +392,15 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro // ID and size related properties. if (tiles.size() == 1) { if (tiles.front()->get().alternative == 0) { - p_list->push_back(PropertyInfo(Variant::VECTOR2I, "atlas_coords", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::NIL, TTR("Base Tile"), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("atlas_coords"))); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("size_in_atlas"))); } else { - p_list->push_back(PropertyInfo(Variant::INT, "alternative_id", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::NIL, TTR("Alternative Tile"), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("alternative_id"))); } + } else { + p_list->push_back(PropertyInfo(Variant::NIL, TTR("Tiles"), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); } // Animation. @@ -398,17 +414,17 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro } if (all_alternatve_id_zero) { - p_list->push_back(PropertyInfo(Variant::NIL, "Animation", PROPERTY_HINT_NONE, "animation_", PROPERTY_USAGE_GROUP)); - p_list->push_back(PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::VECTOR2I, "animation_separation", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_")); + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Animation", "animation_"), PROPERTY_HINT_NONE, "animation_", PROPERTY_USAGE_GROUP)); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("animation_columns"))); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, PNAME("animation_separation"))); + p_list->push_back(PropertyInfo(Variant::FLOAT, PNAME("animation_speed"))); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("animation_frames_count"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_")); // Not optimal, but returns value for the first tile. This is similar to what MultiNodeEdit does. if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) { - p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); } else { for (int i = 0; i < tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile); i++) { - p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/%s", i, PNAME("duration")), PROPERTY_HINT_NONE, "suffix:s")); } } } @@ -425,21 +441,26 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro int uses = 0; PropertyInfo property_info; }; - Map<PropertyId, PLData> usage; + RBMap<PropertyId, PLData> usage; List<PLData *> data_list; - for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { - const Vector2i &coords = E->get().tile; - const int &alternative = E->get().alternative; + for (const TileSelection &E : tiles) { + const Vector2i &coords = E.tile; + const int &alternative = E.alternative; - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative); ERR_FAIL_COND(!tile_data); List<PropertyInfo> list; tile_data->get_property_list(&list); - Map<String, int> counts; // Counts the number of time a property appears (useful for groups that may appear more than once) + HashMap<String, int> counts; // Counts the number of time a property appears (useful for groups that may appear more than once) for (List<PropertyInfo>::Element *E_property = list.front(); E_property; E_property = E_property->next()) { + // Don't show category for TileData. + if (E_property->get().usage & PROPERTY_USAGE_CATEGORY) { + continue; + } + const String &property_string = E_property->get().name; if (!tile_data->is_allowing_transform() && (property_string == "flip_h" || property_string == "flip_v" || property_string == "transpose")) { continue; @@ -472,21 +493,21 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro } } -void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles) { +void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(TileSetAtlasSource *p_tile_set_atlas_source, RBSet<TileSelection> p_tiles) { ERR_FAIL_COND(!p_tile_set_atlas_source); ERR_FAIL_COND(p_tiles.is_empty()); - for (Set<TileSelection>::Element *E = p_tiles.front(); E; E = E->next()) { - ERR_FAIL_COND(E->get().tile == TileSetSource::INVALID_ATLAS_COORDS); - ERR_FAIL_COND(E->get().alternative < 0); + for (const TileSelection &E : p_tiles) { + ERR_FAIL_COND(E.tile == TileSetSource::INVALID_ATLAS_COORDS); + ERR_FAIL_COND(E.alternative < 0); } // Disconnect to changes. - for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { - const Vector2i &coords = E->get().tile; - const int &alternative = E->get().alternative; + for (const TileSelection &E : tiles) { + const Vector2i &coords = E.tile; + const int &alternative = E.alternative; if (tile_set_atlas_source && tile_set_atlas_source->has_tile(coords) && tile_set_atlas_source->has_alternative_tile(coords, alternative)) { - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative); if (tile_data->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) { tile_data->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); } @@ -494,15 +515,15 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(TileSetAtlasSource *p_ } tile_set_atlas_source = p_tile_set_atlas_source; - tiles = Set<TileSelection>(p_tiles); + tiles = RBSet<TileSelection>(p_tiles); // Connect to changes. - for (Set<TileSelection>::Element *E = p_tiles.front(); E; E = E->next()) { - const Vector2i &coords = E->get().tile; - const int &alternative = E->get().alternative; + for (const TileSelection &E : p_tiles) { + const Vector2i &coords = E.tile; + const int &alternative = E.alternative; if (tile_set_atlas_source->has_tile(coords) && tile_set_atlas_source->has_alternative_tile(coords, alternative)) { - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative); if (!tile_data->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) { tile_data->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); } @@ -526,7 +547,7 @@ void TileSetAtlasSourceEditor::_update_tile_id_label() { if (selection.size() == 1) { TileSelection selected = selection.front()->get(); tool_tile_id_label->set_text(vformat("%d, %s, %d", tile_set_atlas_source_id, selected.tile, selected.alternative)); - tool_tile_id_label->set_tooltip(vformat(TTR("Selected tile:\nSource: %d\nAtlas coordinates: %s\nAlternative: %d"), tile_set_atlas_source_id, selected.tile, selected.alternative)); + tool_tile_id_label->set_tooltip_text(vformat(TTR("Selected tile:\nSource: %d\nAtlas coordinates: %s\nAlternative: %d"), tile_set_atlas_source_id, selected.tile, selected.alternative)); tool_tile_id_label->show(); } else { tool_tile_id_label->hide(); @@ -540,11 +561,13 @@ void TileSetAtlasSourceEditor::_update_source_inspector() { void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() { // Fix selected. - for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { + for (RBSet<TileSelection>::Element *E = selection.front(); E;) { + RBSet<TileSelection>::Element *N = E->next(); TileSelection selected = E->get(); if (!tile_set_atlas_source->has_tile(selected.tile) || !tile_set_atlas_source->has_alternative_tile(selected.tile, selected.alternative)) { selection.erase(E); } + E = N; } // Fix hovered. @@ -560,9 +583,9 @@ void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() { void TileSetAtlasSourceEditor::_update_atlas_source_inspector() { // Update visibility. - bool visible = tools_button_group->get_pressed_button() == tool_setup_atlas_source_button; - atlas_source_inspector_label->set_visible(visible); - atlas_source_inspector->set_visible(visible); + bool inspector_visible = tools_button_group->get_pressed_button() == tool_setup_atlas_source_button; + atlas_source_inspector->set_visible(inspector_visible); + atlas_source_inspector->set_read_only(read_only); } void TileSetAtlasSourceEditor::_update_tile_inspector() { @@ -571,14 +594,13 @@ void TileSetAtlasSourceEditor::_update_tile_inspector() { if (!selection.is_empty()) { tile_proxy_object->edit(tile_set_atlas_source, selection); } - tile_inspector_label->show(); tile_inspector->set_visible(!selection.is_empty()); tile_inspector_no_tile_selected_label->set_visible(selection.is_empty()); } else { - tile_inspector_label->hide(); tile_inspector->hide(); tile_inspector_no_tile_selected_label->hide(); } + tile_inspector->set_read_only(read_only); } void TileSetAtlasSourceEditor::_update_tile_data_editors() { @@ -589,6 +611,10 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { tile_data_editors_tree->clear(); + if (tile_set.is_null()) { + return; + } + TreeItem *root = tile_data_editors_tree->create_item(); TreeItem *group; @@ -609,147 +635,158 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { } // Theming. - tile_data_editors_tree->add_theme_constant_override("vseparation", 1); - tile_data_editors_tree->add_theme_constant_override("hseparation", 3); + tile_data_editors_tree->add_theme_constant_override("v_separation", 1); + tile_data_editors_tree->add_theme_constant_override("h_separation", 3); Color group_color = get_theme_color(SNAME("prop_category"), SNAME("Editor")); // List of editors. // --- Rendering --- - ADD_TILE_DATA_EDITOR_GROUP("Rendering"); + ADD_TILE_DATA_EDITOR_GROUP(TTR("Rendering")); - ADD_TILE_DATA_EDITOR(group, "Texture Offset", "texture_offset"); - if (!tile_data_editors.has("texture_offset")) { - TileDataTextureOffsetEditor *tile_data_texture_offset_editor = memnew(TileDataTextureOffsetEditor); - tile_data_texture_offset_editor->hide(); - tile_data_texture_offset_editor->setup_property_editor(Variant::VECTOR2, "texture_offset"); - tile_data_texture_offset_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); - tile_data_texture_offset_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); - tile_data_editors["texture_offset"] = tile_data_texture_offset_editor; + ADD_TILE_DATA_EDITOR(group, TTR("Texture Origin"), "texture_origin"); + if (!tile_data_editors.has("texture_origin")) { + TileDataTextureOriginEditor *tile_data_texture_origin_editor = memnew(TileDataTextureOriginEditor); + tile_data_texture_origin_editor->hide(); + tile_data_texture_origin_editor->setup_property_editor(Variant::VECTOR2, "texture_origin"); + tile_data_texture_origin_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw)); + tile_data_texture_origin_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw)); + tile_data_editors["texture_origin"] = tile_data_texture_origin_editor; } - ADD_TILE_DATA_EDITOR(group, "Modulate", "modulate"); + ADD_TILE_DATA_EDITOR(group, TTR("Modulate"), "modulate"); if (!tile_data_editors.has("modulate")) { TileDataDefaultEditor *tile_data_modulate_editor = memnew(TileDataDefaultEditor()); tile_data_modulate_editor->hide(); tile_data_modulate_editor->setup_property_editor(Variant::COLOR, "modulate", "", Color(1.0, 1.0, 1.0, 1.0)); - tile_data_modulate_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); - tile_data_modulate_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_modulate_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw)); + tile_data_modulate_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw)); tile_data_editors["modulate"] = tile_data_modulate_editor; } - ADD_TILE_DATA_EDITOR(group, "Z Index", "z_index"); + ADD_TILE_DATA_EDITOR(group, TTR("Z Index"), "z_index"); if (!tile_data_editors.has("z_index")) { TileDataDefaultEditor *tile_data_z_index_editor = memnew(TileDataDefaultEditor()); tile_data_z_index_editor->hide(); tile_data_z_index_editor->setup_property_editor(Variant::INT, "z_index"); - tile_data_z_index_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); - tile_data_z_index_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_z_index_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw)); + tile_data_z_index_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw)); tile_data_editors["z_index"] = tile_data_z_index_editor; } - ADD_TILE_DATA_EDITOR(group, "Y Sort Origin", "y_sort_origin"); + ADD_TILE_DATA_EDITOR(group, TTR("Y Sort Origin"), "y_sort_origin"); if (!tile_data_editors.has("y_sort_origin")) { TileDataYSortEditor *tile_data_y_sort_editor = memnew(TileDataYSortEditor); tile_data_y_sort_editor->hide(); tile_data_y_sort_editor->setup_property_editor(Variant::INT, "y_sort_origin"); - tile_data_y_sort_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); - tile_data_y_sort_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_y_sort_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw)); + tile_data_y_sort_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw)); tile_data_editors["y_sort_origin"] = tile_data_y_sort_editor; } for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) { - ADD_TILE_DATA_EDITOR(group, vformat("Occlusion Layer %d", i), vformat("occlusion_layer_%d", i)); + ADD_TILE_DATA_EDITOR(group, vformat(TTR("Occlusion Layer %d"), i), vformat("occlusion_layer_%d", i)); if (!tile_data_editors.has(vformat("occlusion_layer_%d", i))) { TileDataOcclusionShapeEditor *tile_data_occlusion_shape_editor = memnew(TileDataOcclusionShapeEditor()); tile_data_occlusion_shape_editor->hide(); tile_data_occlusion_shape_editor->set_occlusion_layer(i); - tile_data_occlusion_shape_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); - tile_data_occlusion_shape_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_occlusion_shape_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw)); + tile_data_occlusion_shape_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw)); tile_data_editors[vformat("occlusion_layer_%d", i)] = tile_data_occlusion_shape_editor; } } for (int i = tile_set->get_occlusion_layers_count(); tile_data_editors.has(vformat("occlusion_layer_%d", i)); i++) { - tile_data_editors[vformat("occlusion_layer_%d", i)]->queue_delete(); + tile_data_editors[vformat("occlusion_layer_%d", i)]->queue_free(); tile_data_editors.erase(vformat("occlusion_layer_%d", i)); } // --- Rendering --- - ADD_TILE_DATA_EDITOR(root, "Terrains", "terrain_set"); + ADD_TILE_DATA_EDITOR(root, TTR("Terrains"), "terrain_set"); if (!tile_data_editors.has("terrain_set")) { TileDataTerrainsEditor *tile_data_terrains_editor = memnew(TileDataTerrainsEditor); tile_data_terrains_editor->hide(); - tile_data_terrains_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); - tile_data_terrains_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_terrains_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw)); + tile_data_terrains_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw)); tile_data_editors["terrain_set"] = tile_data_terrains_editor; } // --- Miscellaneous --- - ADD_TILE_DATA_EDITOR(root, "Probability", "probability"); + ADD_TILE_DATA_EDITOR(root, TTR("Probability"), "probability"); if (!tile_data_editors.has("probability")) { TileDataDefaultEditor *tile_data_probability_editor = memnew(TileDataDefaultEditor()); tile_data_probability_editor->hide(); tile_data_probability_editor->setup_property_editor(Variant::FLOAT, "probability", "", 1.0); - tile_data_probability_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); - tile_data_probability_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_probability_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw)); + tile_data_probability_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw)); tile_data_editors["probability"] = tile_data_probability_editor; } // --- Physics --- - ADD_TILE_DATA_EDITOR_GROUP("Physics"); + ADD_TILE_DATA_EDITOR_GROUP(TTR("Physics")); for (int i = 0; i < tile_set->get_physics_layers_count(); i++) { - ADD_TILE_DATA_EDITOR(group, vformat("Physics Layer %d", i), vformat("physics_layer_%d", i)); + ADD_TILE_DATA_EDITOR(group, vformat(TTR("Physics Layer %d"), i), vformat("physics_layer_%d", i)); if (!tile_data_editors.has(vformat("physics_layer_%d", i))) { TileDataCollisionEditor *tile_data_collision_editor = memnew(TileDataCollisionEditor()); tile_data_collision_editor->hide(); tile_data_collision_editor->set_physics_layer(i); - tile_data_collision_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); - tile_data_collision_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_collision_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw)); + tile_data_collision_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw)); tile_data_editors[vformat("physics_layer_%d", i)] = tile_data_collision_editor; } } for (int i = tile_set->get_physics_layers_count(); tile_data_editors.has(vformat("physics_layer_%d", i)); i++) { - tile_data_editors[vformat("physics_layer_%d", i)]->queue_delete(); + tile_data_editors[vformat("physics_layer_%d", i)]->queue_free(); tile_data_editors.erase(vformat("physics_layer_%d", i)); } // --- Navigation --- - ADD_TILE_DATA_EDITOR_GROUP("Navigation"); + ADD_TILE_DATA_EDITOR_GROUP(TTR("Navigation")); for (int i = 0; i < tile_set->get_navigation_layers_count(); i++) { - ADD_TILE_DATA_EDITOR(group, vformat("Navigation Layer %d", i), vformat("navigation_layer_%d", i)); + ADD_TILE_DATA_EDITOR(group, vformat(TTR("Navigation Layer %d"), i), vformat("navigation_layer_%d", i)); if (!tile_data_editors.has(vformat("navigation_layer_%d", i))) { TileDataNavigationEditor *tile_data_navigation_editor = memnew(TileDataNavigationEditor()); tile_data_navigation_editor->hide(); tile_data_navigation_editor->set_navigation_layer(i); - tile_data_navigation_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); - tile_data_navigation_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_navigation_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw)); + tile_data_navigation_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw)); tile_data_editors[vformat("navigation_layer_%d", i)] = tile_data_navigation_editor; } } for (int i = tile_set->get_navigation_layers_count(); tile_data_editors.has(vformat("navigation_layer_%d", i)); i++) { - tile_data_editors[vformat("navigation_layer_%d", i)]->queue_delete(); + tile_data_editors[vformat("navigation_layer_%d", i)]->queue_free(); tile_data_editors.erase(vformat("navigation_layer_%d", i)); } // --- Custom Data --- - ADD_TILE_DATA_EDITOR_GROUP("Custom Data"); + ADD_TILE_DATA_EDITOR_GROUP(TTR("Custom Data")); for (int i = 0; i < tile_set->get_custom_data_layers_count(); i++) { - if (tile_set->get_custom_data_name(i).is_empty()) { - ADD_TILE_DATA_EDITOR(group, vformat("Custom Data %d", i), vformat("custom_data_%d", i)); + String editor_name = vformat("custom_data_%d", i); + String prop_name = tile_set->get_custom_data_layer_name(i); + Variant::Type prop_type = tile_set->get_custom_data_layer_type(i); + + if (prop_name.is_empty()) { + ADD_TILE_DATA_EDITOR(group, vformat(TTR("Custom Data %d"), i), editor_name); } else { - ADD_TILE_DATA_EDITOR(group, tile_set->get_custom_data_name(i), vformat("custom_data_%d", i)); + ADD_TILE_DATA_EDITOR(group, prop_name, editor_name); } - if (!tile_data_editors.has(vformat("custom_data_%d", i))) { + + // If the type of the edited property has been changed, delete the + // editor and create a new one. + if (tile_data_editors.has(editor_name) && ((TileDataDefaultEditor *)tile_data_editors[editor_name])->get_property_type() != prop_type) { + tile_data_editors[vformat("custom_data_%d", i)]->queue_free(); + tile_data_editors.erase(vformat("custom_data_%d", i)); + } + if (!tile_data_editors.has(editor_name)) { TileDataDefaultEditor *tile_data_custom_data_editor = memnew(TileDataDefaultEditor()); tile_data_custom_data_editor->hide(); - tile_data_custom_data_editor->setup_property_editor(tile_set->get_custom_data_type(i), vformat("custom_data_%d", i), tile_set->get_custom_data_name(i)); - tile_data_custom_data_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); - tile_data_custom_data_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); - tile_data_editors[vformat("custom_data_%d", i)] = tile_data_custom_data_editor; + tile_data_custom_data_editor->setup_property_editor(prop_type, editor_name, prop_name); + tile_data_custom_data_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::queue_redraw)); + tile_data_custom_data_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::queue_redraw)); + tile_data_editors[editor_name] = tile_data_custom_data_editor; } } for (int i = tile_set->get_custom_data_layers_count(); tile_data_editors.has(vformat("custom_data_%d", i)); i++) { - tile_data_editors[vformat("custom_data_%d", i)]->queue_delete(); + tile_data_editors[vformat("custom_data_%d", i)]->queue_free(); tile_data_editors.erase(vformat("custom_data_%d", i)); } @@ -781,7 +818,7 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { } else { tile_data_editor_dropdown_button->set_text(TTR("Select a property editor")); } - tile_data_editors_label->set_visible(is_visible); + tile_data_editors_scroll->set_visible(is_visible); } void TileSetAtlasSourceEditor::_update_current_tile_data_editor() { @@ -871,10 +908,10 @@ void TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_pressed() { void TileSetAtlasSourceEditor::_tile_data_editors_tree_selected() { tile_data_editors_popup->call_deferred(SNAME("hide")); _update_current_tile_data_editor(); - tile_atlas_control->update(); - tile_atlas_control_unscaled->update(); - alternative_tiles_control->update(); - alternative_tiles_control_unscaled->update(); + tile_atlas_control->queue_redraw(); + tile_atlas_control_unscaled->queue_redraw(); + alternative_tiles_control->queue_redraw(); + alternative_tiles_control_unscaled->queue_redraw(); } void TileSetAtlasSourceEditor::_update_atlas_view() { @@ -883,7 +920,11 @@ void TileSetAtlasSourceEditor::_update_atlas_view() { // Create a bunch of buttons to add alternative tiles. for (int i = 0; i < alternative_tiles_control->get_child_count(); i++) { - alternative_tiles_control->get_child(i)->queue_delete(); + alternative_tiles_control->get_child(i)->queue_free(); + } + + if (tile_set.is_null()) { + return; } Vector2i pos; @@ -912,7 +953,7 @@ void TileSetAtlasSourceEditor::_update_atlas_view() { button->add_theme_style_override("hover", memnew(StyleBoxEmpty)); button->add_theme_style_override("focus", memnew(StyleBoxEmpty)); button->add_theme_style_override("pressed", memnew(StyleBoxEmpty)); - button->connect("pressed", callable_mp(tile_set_atlas_source, &TileSetAtlasSource::create_alternative_tile), varray(tile_id, TileSetSource::INVALID_TILE_ALTERNATIVE)); + button->connect("pressed", callable_mp(tile_set_atlas_source, &TileSetAtlasSource::create_alternative_tile).bind(tile_id, TileSetSource::INVALID_TILE_ALTERNATIVE)); button->set_rect(Rect2(Vector2(pos.x, pos.y + (y_increment - texture_region_base_size.y) / 2.0), Vector2(texture_region_base_size_min, texture_region_base_size_min))); button->set_expand_icon(true); @@ -922,11 +963,11 @@ void TileSetAtlasSourceEditor::_update_atlas_view() { tile_atlas_view->set_padding(Side::SIDE_RIGHT, texture_region_base_size_min); // Redraw everything. - tile_atlas_control->update(); - tile_atlas_control_unscaled->update(); - alternative_tiles_control->update(); - alternative_tiles_control_unscaled->update(); - tile_atlas_view->update(); + tile_atlas_control->queue_redraw(); + tile_atlas_control_unscaled->queue_redraw(); + alternative_tiles_control->queue_redraw(); + alternative_tiles_control_unscaled->queue_redraw(); + tile_atlas_view->queue_redraw(); // Synchronize atlas view. TilesEditorPlugin::get_singleton()->synchronize_atlas_view(tile_atlas_view); @@ -938,36 +979,33 @@ void TileSetAtlasSourceEditor::_update_toolbar() { if (current_tile_data_editor_toolbar) { current_tile_data_editor_toolbar->hide(); } - tool_settings_vsep->show(); tools_settings_erase_button->show(); - tool_advanced_menu_buttom->show(); + tool_advanced_menu_button->show(); } else if (tools_button_group->get_pressed_button() == tool_select_button) { if (current_tile_data_editor_toolbar) { current_tile_data_editor_toolbar->hide(); } - tool_settings_vsep->hide(); tools_settings_erase_button->hide(); - tool_advanced_menu_buttom->hide(); + tool_advanced_menu_button->hide(); } else if (tools_button_group->get_pressed_button() == tool_paint_button) { if (current_tile_data_editor_toolbar) { current_tile_data_editor_toolbar->show(); } - tool_settings_vsep->hide(); tools_settings_erase_button->hide(); - tool_advanced_menu_buttom->hide(); + tool_advanced_menu_button->hide(); } } void TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited() { hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS; - tile_atlas_control->update(); - tile_atlas_control_unscaled->update(); - tile_atlas_view->update(); + tile_atlas_control->queue_redraw(); + tile_atlas_control_unscaled->queue_redraw(); + tile_atlas_view->queue_redraw(); } void TileSetAtlasSourceEditor::_tile_atlas_view_transform_changed() { - tile_atlas_control->update(); - tile_atlas_control_unscaled->update(); + tile_atlas_control->queue_redraw(); + tile_atlas_control_unscaled->queue_redraw(); } void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEvent> &p_event) { @@ -982,54 +1020,23 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven // Update only what's needed. tile_set_changed_needs_update = false; - tile_atlas_control->update(); - tile_atlas_control_unscaled->update(); - alternative_tiles_control->update(); - alternative_tiles_control_unscaled->update(); - tile_atlas_view->update(); + tile_atlas_control->queue_redraw(); + tile_atlas_control_unscaled->queue_redraw(); + alternative_tiles_control->queue_redraw(); + alternative_tiles_control_unscaled->queue_redraw(); + tile_atlas_view->queue_redraw(); return; } else { // Handle the event. Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); - Vector2i last_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_mouse_pos); - Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true); + Vector2i last_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_mouse_pos, true); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true); Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); - if (drag_type == DRAG_TYPE_NONE) { - if (selection.size() == 1) { - // Change the cursor depending on the hovered thing. - TileSelection selected = selection.front()->get(); - if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { - Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position(); - Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); - Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); - Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); - Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); - const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; - const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; - CursorShape cursor_shape = CURSOR_ARROW; - bool can_grow[4]; - for (int i = 0; i < 4; i++) { - can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile); - can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; - } - for (int i = 0; i < 4; i++) { - Vector2 pos = rect.position + rect.size * coords[i]; - if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { - cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; - } - Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; - if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { - cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; - } - } - tile_atlas_control->set_default_cursor_shape(cursor_shape); - } - } - } else if (drag_type == DRAG_TYPE_CREATE_BIG_TILE) { + if (drag_type == DRAG_TYPE_CREATE_BIG_TILE) { // Create big tile. new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); @@ -1074,7 +1081,6 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven // Move tile. Vector2 mouse_offset = (Vector2(tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size(); Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position() - mouse_offset); - coords = coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); if (drag_current_tile != coords && tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile), tile_set_atlas_source->get_tile_animation_columns(drag_current_tile), tile_set_atlas_source->get_tile_animation_separation(drag_current_tile), tile_set_atlas_source->get_tile_animation_frames_count(drag_current_tile), drag_current_tile)) { tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, coords); selection.clear(); @@ -1131,11 +1137,11 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven } // Redraw for the hovered tile. - tile_atlas_control->update(); - tile_atlas_control_unscaled->update(); - alternative_tiles_control->update(); - alternative_tiles_control_unscaled->update(); - tile_atlas_view->update(); + tile_atlas_control->queue_redraw(); + tile_atlas_control_unscaled->queue_redraw(); + alternative_tiles_control->queue_redraw(); + alternative_tiles_control_unscaled->queue_redraw(); + tile_atlas_view->queue_redraw(); return; } @@ -1220,7 +1226,6 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; - CursorShape cursor_shape = CURSOR_ARROW; bool can_grow[4]; for (int i = 0; i < 4; i++) { can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile); @@ -1234,7 +1239,6 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_last_mouse_pos = drag_start_mouse_pos; drag_current_tile = selected.tile; drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); - cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; } Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { @@ -1243,10 +1247,8 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_last_mouse_pos = drag_start_mouse_pos; drag_current_tile = selected.tile; drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); - cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; } } - tile_atlas_control->set_default_cursor_shape(cursor_shape); } } @@ -1269,7 +1271,6 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_last_mouse_pos = drag_start_mouse_pos; drag_current_tile = selected.tile; drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); - tile_atlas_control->set_default_cursor_shape(CURSOR_MOVE); } else { // Start selection dragging. drag_type = DRAG_TYPE_RECT_SELECT; @@ -1282,11 +1283,11 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven // Left click released. _end_dragging(); } - tile_atlas_control->update(); - tile_atlas_control_unscaled->update(); - alternative_tiles_control->update(); - alternative_tiles_control_unscaled->update(); - tile_atlas_view->update(); + tile_atlas_control->queue_redraw(); + tile_atlas_control_unscaled->queue_redraw(); + alternative_tiles_control->queue_redraw(); + alternative_tiles_control_unscaled->queue_redraw(); + tile_atlas_view->queue_redraw(); return; } else if (mb->get_button_index() == MouseButton::RIGHT) { // Right click pressed. @@ -1297,11 +1298,11 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven // Right click released. _end_dragging(); } - tile_atlas_control->update(); - tile_atlas_control_unscaled->update(); - alternative_tiles_control->update(); - alternative_tiles_control_unscaled->update(); - tile_atlas_view->update(); + tile_atlas_control->queue_redraw(); + tile_atlas_control_unscaled->queue_redraw(); + alternative_tiles_control->queue_redraw(); + alternative_tiles_control_unscaled->queue_redraw(); + tile_atlas_view->queue_redraw(); return; } } @@ -1309,12 +1310,13 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven } void TileSetAtlasSourceEditor::_end_dragging() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); switch (drag_type) { case DRAG_TYPE_CREATE_TILES: undo_redo->create_action(TTR("Create tiles")); - for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_set_atlas_source, "create_tile", E->get()); - undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", E->get()); + for (const Vector2i &E : drag_modified_tiles) { + undo_redo->add_do_method(tile_set_atlas_source, "create_tile", E); + undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", E); } undo_redo->commit_action(false); break; @@ -1327,10 +1329,10 @@ void TileSetAtlasSourceEditor::_end_dragging() { case DRAG_TYPE_REMOVE_TILES: { List<PropertyInfo> list; tile_set_atlas_source->get_property_list(&list); - Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); + HashMap<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); undo_redo->create_action(TTR("Remove tiles")); - for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) { - Vector2i coords = E->get(); + for (const Vector2i &E : drag_modified_tiles) { + Vector2i coords = E; undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords); undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords); if (per_tile.has(coords)) { @@ -1346,8 +1348,8 @@ void TileSetAtlasSourceEditor::_end_dragging() { undo_redo->commit_action(); } break; case DRAG_TYPE_CREATE_TILES_USING_RECT: { - Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); - Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true); Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size())); undo_redo->create_action(TTR("Create tiles")); @@ -1363,15 +1365,15 @@ void TileSetAtlasSourceEditor::_end_dragging() { undo_redo->commit_action(); } break; case DRAG_TYPE_REMOVE_TILES_USING_RECT: { - Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); - Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true); Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size())); List<PropertyInfo> list; tile_set_atlas_source->get_property_list(&list); - Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); + HashMap<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); - Set<Vector2i> to_delete; + RBSet<Vector2i> to_delete; for (int x = area.get_position().x; x < area.get_end().x; x++) { for (int y = area.get_position().y; y < area.get_end().y; y++) { Vector2i coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y)); @@ -1383,8 +1385,8 @@ void TileSetAtlasSourceEditor::_end_dragging() { undo_redo->create_action(TTR("Remove tiles")); undo_redo->add_do_method(this, "_set_selection_from_array", Array()); - for (Set<Vector2i>::Element *E = to_delete.front(); E; E = E->next()) { - Vector2i coords = E->get(); + for (const Vector2i &E : to_delete) { + Vector2i coords = E; undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords); undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords); if (per_tile.has(coords)) { @@ -1414,8 +1416,8 @@ void TileSetAtlasSourceEditor::_end_dragging() { } break; case DRAG_TYPE_RECT_SELECT: { - Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); - Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true); ERR_FAIL_COND(start_base_tiles_coords == TileSetSource::INVALID_ATLAS_COORDS); ERR_FAIL_COND(new_base_tiles_coords == TileSetSource::INVALID_ATLAS_COORDS); @@ -1485,12 +1487,12 @@ void TileSetAtlasSourceEditor::_end_dragging() { // We have a tile. menu_option_coords = selected.tile; menu_option_alternative = 0; - base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); + base_tile_popup_menu->popup(Rect2i(get_screen_transform().xform(get_local_mouse_position()), Size2i())); } else if (hovered_base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) { // We don't have a tile, but can create one. menu_option_coords = hovered_base_tile_coords; menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE; - empty_base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); + empty_base_tile_popup_menu->popup(Rect2i(get_screen_transform().xform(get_local_mouse_position()), Size2i())); } } break; case DRAG_TYPE_RESIZE_TOP_LEFT: @@ -1519,12 +1521,72 @@ void TileSetAtlasSourceEditor::_end_dragging() { drag_modified_tiles.clear(); drag_type = DRAG_TYPE_NONE; - tile_atlas_control->set_default_cursor_shape(CURSOR_ARROW); + // Change mouse accordingly. } -Map<Vector2i, List<const PropertyInfo *>> TileSetAtlasSourceEditor::_group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas) { +Control::CursorShape TileSetAtlasSourceEditor::get_cursor_shape(const Point2 &p_pos) const { + Control::CursorShape cursor_shape = get_default_cursor_shape(); + if (drag_type == DRAG_TYPE_NONE) { + if (selection.size() == 1) { + // Change the cursor depending on the hovered thing. + TileSelection selected = selection.front()->get(); + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { + Transform2D xform = tile_atlas_control->get_global_transform().affine_inverse() * get_global_transform(); + Vector2 mouse_local_pos = xform.xform(p_pos); + Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); + Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); + Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); + Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); + const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; + const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; + bool can_grow[4]; + for (int i = 0; i < 4; i++) { + can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile); + can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; + } + for (int i = 0; i < 4; i++) { + Vector2 pos = rect.position + rect.size * coords[i]; + if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { + cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; + } + Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; + if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { + cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; + } + } + } + } + } else { + switch (drag_type) { + case DRAG_TYPE_RESIZE_TOP_LEFT: + case DRAG_TYPE_RESIZE_BOTTOM_RIGHT: + cursor_shape = CURSOR_FDIAGSIZE; + break; + case DRAG_TYPE_RESIZE_TOP: + case DRAG_TYPE_RESIZE_BOTTOM: + cursor_shape = CURSOR_VSIZE; + break; + case DRAG_TYPE_RESIZE_TOP_RIGHT: + case DRAG_TYPE_RESIZE_BOTTOM_LEFT: + cursor_shape = CURSOR_BDIAGSIZE; + break; + case DRAG_TYPE_RESIZE_LEFT: + case DRAG_TYPE_RESIZE_RIGHT: + cursor_shape = CURSOR_HSIZE; + break; + case DRAG_TYPE_MOVE_TILE: + cursor_shape = CURSOR_MOVE; + break; + default: + break; + } + } + return cursor_shape; +} + +HashMap<Vector2i, List<const PropertyInfo *>> TileSetAtlasSourceEditor::_group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas) { // Group properties per tile. - Map<Vector2i, List<const PropertyInfo *>> per_tile; + HashMap<Vector2i, List<const PropertyInfo *>> per_tile; for (const List<PropertyInfo>::Element *E_property = r_list.front(); E_property; E_property = E_property->next()) { Vector<String> components = String(E_property->get().name).split("/", true, 1); if (components.size() >= 1) { @@ -1539,17 +1601,19 @@ Map<Vector2i, List<const PropertyInfo *>> TileSetAtlasSourceEditor::_group_prope } void TileSetAtlasSourceEditor::_menu_option(int p_option) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + switch (p_option) { case TILE_DELETE: { List<PropertyInfo> list; tile_set_atlas_source->get_property_list(&list); - Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); + HashMap<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); undo_redo->create_action(TTR("Remove tile")); // Remove tiles - Set<Vector2i> removed; - for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { - TileSelection selected = E->get(); + RBSet<Vector2i> removed; + for (const TileSelection &E : selection) { + TileSelection selected = E; if (selected.alternative == 0) { // Remove a tile. undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", selected.tile); @@ -1568,8 +1632,8 @@ void TileSetAtlasSourceEditor::_menu_option(int p_option) { } // Remove alternatives - for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { - TileSelection selected = E->get(); + for (const TileSelection &E : selection) { + TileSelection selected = E; if (selected.alternative > 0 && !removed.has(selected.tile)) { // Remove an alternative tile. undo_redo->add_do_method(tile_set_atlas_source, "remove_alternative_tile", selected.tile, selected.alternative); @@ -1607,13 +1671,13 @@ void TileSetAtlasSourceEditor::_menu_option(int p_option) { case TILE_CREATE_ALTERNATIVE: { undo_redo->create_action(TTR("Create tile alternatives")); Array array; - for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { - if (E->get().alternative == 0) { - int next_id = tile_set_atlas_source->get_next_alternative_tile_id(E->get().tile); - undo_redo->add_do_method(tile_set_atlas_source, "create_alternative_tile", E->get().tile, next_id); - array.push_back(E->get().tile); + for (const TileSelection &E : selection) { + if (E.alternative == 0) { + int next_id = tile_set_atlas_source->get_next_alternative_tile_id(E.tile); + undo_redo->add_do_method(tile_set_atlas_source, "create_alternative_tile", E.tile, next_id); + array.push_back(E.tile); array.push_back(next_id); - undo_redo->add_undo_method(tile_set_atlas_source, "remove_alternative_tile", E->get().tile, next_id); + undo_redo->add_undo_method(tile_set_atlas_source, "remove_alternative_tile", E.tile, next_id); } } undo_redo->add_do_method(this, "_set_selection_from_array", array); @@ -1657,31 +1721,24 @@ void TileSetAtlasSourceEditor::_set_selection_from_array(Array p_selection) { Array TileSetAtlasSourceEditor::_get_selection_as_array() { Array output; - for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { - output.push_back(E->get().tile); - output.push_back(E->get().alternative); + for (const TileSelection &E : selection) { + output.push_back(E.tile); + output.push_back(E.alternative); } return output; } void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { - // Colors. - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); - Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); - // Draw the selected tile. if (tools_button_group->get_pressed_button() == tool_select_button) { - for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { - TileSelection selected = E->get(); + for (const TileSelection &E : selection) { + TileSelection selected = E; if (selected.alternative == 0) { // Draw the rect. for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(selected.tile); frame++) { - Color color = selection_color; - if (frame > 0) { - color.a *= 0.3; - } + Color color = Color(0.0, 1.0, 0.0, frame == 0 ? 1.0 : 0.3); Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile, frame); - tile_atlas_control->draw_rect(region, color, false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, region, color); } } } @@ -1694,8 +1751,8 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); - Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; - Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; + const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; + const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; bool can_grow[4]; for (int i = 0; i < 4; i++) { can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile); @@ -1721,24 +1778,24 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { if (drag_type == DRAG_TYPE_REMOVE_TILES) { // Draw the tiles to be removed. - for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) { - for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(E->get()); frame++) { - tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(E->get(), frame), Color(0.0, 0.0, 0.0), false); + for (const Vector2i &E : drag_modified_tiles) { + for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(E); frame++) { + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, tile_set_atlas_source->get_tile_texture_region(E, frame), Color(0.0, 0.0, 0.0)); } } } else if (drag_type == DRAG_TYPE_RECT_SELECT || drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT) { // Draw tiles to be removed. - Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); - Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true); Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size())); Color color = Color(0.0, 0.0, 0.0); if (drag_type == DRAG_TYPE_RECT_SELECT) { - color = selection_color.lightened(0.2); + color = Color(1.0, 1.0, 0.0); } - Set<Vector2i> to_paint; + RBSet<Vector2i> to_paint; for (int x = area.get_position().x; x < area.get_end().x; x++) { for (int y = area.get_position().y; y < area.get_end().y; y++) { Vector2i coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y)); @@ -1748,9 +1805,9 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { } } - for (Set<Vector2i>::Element *E = to_paint.front(); E; E = E->next()) { - Vector2i coords = E->get(); - tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(coords), color, false); + for (const Vector2i &E : to_paint) { + Vector2i coords = E; + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, tile_set_atlas_source->get_tile_texture_region(coords), color); } } else if (drag_type == DRAG_TYPE_CREATE_TILES_USING_RECT) { // Draw tiles to be created. @@ -1758,8 +1815,8 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { Vector2i separation = tile_set_atlas_source->get_separation(); Vector2i tile_size = tile_set_atlas_source->get_texture_region_size(); - Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); - Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true); Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size())); for (int x = area.get_position().x; x < area.get_end().x; x++) { @@ -1767,7 +1824,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { Vector2i coords = Vector2i(x, y); if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { Vector2i origin = margins + (coords * (tile_size + separation)); - tile_atlas_control->draw_rect(Rect2i(origin, tile_size), Color(1.0, 1.0, 1.0), false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, Rect2i(origin, tile_size)); } } } @@ -1776,15 +1833,15 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { // Draw the hovered tile. if (drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT || drag_type == DRAG_TYPE_CREATE_TILES_USING_RECT) { // Draw the rect. - Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); - Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos, true); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position(), true); Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size())); Vector2i margins = tile_set_atlas_source->get_margins(); Vector2i separation = tile_set_atlas_source->get_separation(); Vector2i tile_size = tile_set_atlas_source->get_texture_region_size(); Vector2i origin = margins + (area.position * (tile_size + separation)); - tile_atlas_control->draw_rect(Rect2i(origin, area.size * tile_size), Color(1.0, 1.0, 1.0), false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, Rect2i(origin, area.size * tile_size)); } else { Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); if (hovered_base_tile_coords.x >= 0 && hovered_base_tile_coords.y >= 0 && hovered_base_tile_coords.x < grid_size.x && hovered_base_tile_coords.y < grid_size.y) { @@ -1792,11 +1849,8 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { if (hovered_tile != TileSetSource::INVALID_ATLAS_COORDS) { // Draw existing hovered tile. for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(hovered_tile); frame++) { - Color color = Color(1.0, 1.0, 1.0); - if (frame > 0) { - color.a *= 0.3; - } - tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(hovered_tile, frame), color, false); + Color color = Color(1.0, 0.8, 0.0, frame == 0 ? 0.6 : 0.3); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, tile_set_atlas_source->get_tile_texture_region(hovered_tile, frame), color); } } else { // Draw empty tile, only in add/remove tiles mode. @@ -1805,7 +1859,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { Vector2i separation = tile_set_atlas_source->get_separation(); Vector2i tile_size = tile_set_atlas_source->get_texture_region_size(); Vector2i origin = margins + (hovered_base_tile_coords * (tile_size + separation)); - tile_atlas_control->draw_rect(Rect2i(origin, tile_size), Color(1.0, 1.0, 1.0), false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, Rect2i(origin, tile_size)); } } } @@ -1818,10 +1872,10 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw() { for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { Vector2i coords = tile_set_atlas_source->get_tile_id(i); Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(coords); - Vector2i position = texture_region.get_center() + tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + tile_set_atlas_source->get_tile_data(coords, 0)->get_texture_origin(); Transform2D xform = tile_atlas_control->get_parent_control()->get_transform(); - xform.translate(position); + xform.translate_local(position); if (tools_button_group->get_pressed_button() == tool_select_button && selection.has({ coords, 0 })) { continue; @@ -1836,19 +1890,19 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw() { // Draw the selection on top of other. if (tools_button_group->get_pressed_button() == tool_select_button) { - for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { - if (E->get().alternative != 0) { + for (const TileSelection &E : selection) { + if (E.alternative != 0) { continue; } - Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(E->get().tile); - Vector2i position = texture_region.get_center() + tile_set_atlas_source->get_tile_effective_texture_offset(E->get().tile, 0); + Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(E.tile); + Vector2i position = texture_region.get_center() + tile_set_atlas_source->get_tile_data(E.tile, 0)->get_texture_origin(); Transform2D xform = tile_atlas_control->get_parent_control()->get_transform(); - xform.translate(position); + xform.translate_local(position); TileMapCell cell; cell.source_id = tile_set_atlas_source_id; - cell.set_atlas_coords(E->get().tile); + cell.set_atlas_coords(E.tile); cell.alternative_tile = 0; current_tile_data_editor->draw_over_tile(tile_atlas_control_unscaled, xform, cell, true); } @@ -1871,26 +1925,30 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<In if (current_tile_data_editor) { current_tile_data_editor->forward_painting_alternatives_gui_input(tile_atlas_view, tile_set_atlas_source, p_event); } - tile_atlas_control->update(); - tile_atlas_control_unscaled->update(); - alternative_tiles_control->update(); - alternative_tiles_control_unscaled->update(); - tile_atlas_view->update(); + tile_atlas_control->queue_redraw(); + tile_atlas_control_unscaled->queue_redraw(); + alternative_tiles_control->queue_redraw(); + alternative_tiles_control_unscaled->queue_redraw(); + tile_atlas_view->queue_redraw(); return; } Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - tile_atlas_control->update(); - tile_atlas_control_unscaled->update(); - alternative_tiles_control->update(); - alternative_tiles_control_unscaled->update(); + tile_atlas_control->queue_redraw(); + tile_atlas_control_unscaled->queue_redraw(); + alternative_tiles_control->queue_redraw(); + alternative_tiles_control_unscaled->queue_redraw(); + + if (drag_type == DRAG_TYPE_MAY_POPUP_MENU) { + if (Vector2(drag_start_mouse_pos).distance_to(alternative_tiles_control->get_local_mouse_position()) > 5.0 * EDSCALE) { + drag_type = DRAG_TYPE_NONE; + } + } } Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - drag_type = DRAG_TYPE_NONE; - Vector2 mouse_local_pos = alternative_tiles_control->get_local_mouse_position(); if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { @@ -1910,45 +1968,49 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<In } } else if (mb->get_button_index() == MouseButton::RIGHT) { if (mb->is_pressed()) { - // Right click pressed - Vector3 tile = tile_atlas_view->get_alternative_tile_at_pos(mouse_local_pos); + drag_type = DRAG_TYPE_MAY_POPUP_MENU; + drag_start_mouse_pos = alternative_tiles_control->get_local_mouse_position(); + } else { + if (drag_type == DRAG_TYPE_MAY_POPUP_MENU) { + // Right click released and wasn't dragged too far + Vector3 tile = tile_atlas_view->get_alternative_tile_at_pos(mouse_local_pos); - selection.clear(); - TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) }; - if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { - selection.insert(selected); - } + selection.clear(); + TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) }; + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { + selection.insert(selected); + } - _update_tile_inspector(); - _update_tile_id_label(); + _update_tile_inspector(); + _update_tile_id_label(); - if (selection.size() == 1) { - selected = selection.front()->get(); - menu_option_coords = selected.tile; - menu_option_alternative = selected.alternative; - alternative_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); + if (selection.size() == 1) { + selected = selection.front()->get(); + menu_option_coords = selected.tile; + menu_option_alternative = selected.alternative; + alternative_tile_popup_menu->popup(Rect2i(get_screen_transform().xform(get_local_mouse_position()), Size2i())); + } } + + drag_type = DRAG_TYPE_NONE; } } - tile_atlas_control->update(); - tile_atlas_control_unscaled->update(); - alternative_tiles_control->update(); - alternative_tiles_control_unscaled->update(); + tile_atlas_control->queue_redraw(); + tile_atlas_control_unscaled->queue_redraw(); + alternative_tiles_control->queue_redraw(); + alternative_tiles_control_unscaled->queue_redraw(); } } void TileSetAtlasSourceEditor::_tile_alternatives_control_mouse_exited() { hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE); - tile_atlas_control->update(); - tile_atlas_control_unscaled->update(); - alternative_tiles_control->update(); - alternative_tiles_control_unscaled->update(); + tile_atlas_control->queue_redraw(); + tile_atlas_control_unscaled->queue_redraw(); + alternative_tiles_control->queue_redraw(); + alternative_tiles_control_unscaled->queue_redraw(); } void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() { - Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); - Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); - // Update the hovered alternative tile. if (tools_button_group->get_pressed_button() == tool_select_button) { // Draw hovered tile. @@ -1956,17 +2018,17 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() { if (coords != TileSetSource::INVALID_ATLAS_COORDS) { Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, hovered_alternative_tile_coords.z); if (rect != Rect2i()) { - alternative_tiles_control->draw_rect(rect, Color(1.0, 1.0, 1.0), false); + TilesEditorPlugin::draw_selection_rect(alternative_tiles_control, rect, Color(1.0, 0.8, 0.0, 0.5)); } } // Draw selected tile. - for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { - TileSelection selected = E->get(); + for (const TileSelection &E : selection) { + TileSelection selected = E; if (selected.alternative >= 1) { Rect2i rect = tile_atlas_view->get_alternative_tile_rect(selected.tile, selected.alternative); if (rect != Rect2i()) { - alternative_tiles_control->draw_rect(rect, selection_color, false); + TilesEditorPlugin::draw_selection_rect(alternative_tiles_control, rect); } } } @@ -1985,10 +2047,10 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { continue; } Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); - Vector2 position = rect.get_center(); + Vector2 position = rect.get_center() + tile_set_atlas_source->get_tile_data(coords, alternative_tile)->get_texture_origin(); Transform2D xform = alternative_tiles_control->get_parent_control()->get_transform(); - xform.translate(position); + xform.translate_local(position); if (tools_button_group->get_pressed_button() == tool_select_button && selection.has({ coords, alternative_tile })) { continue; @@ -2004,20 +2066,20 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { // Draw the selection on top of other. if (tools_button_group->get_pressed_button() == tool_select_button) { - for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { - if (E->get().alternative == 0) { + for (const TileSelection &E : selection) { + if (E.alternative == 0) { continue; } - Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E->get().tile, E->get().alternative); - Vector2 position = rect.get_center(); + Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E.tile, E.alternative); + Vector2 position = rect.get_center() + tile_set_atlas_source->get_tile_data(E.tile, E.alternative)->get_texture_origin(); Transform2D xform = alternative_tiles_control->get_parent_control()->get_transform(); - xform.translate(position); + xform.translate_local(position); TileMapCell cell; cell.source_id = tile_set_atlas_source_id; - cell.set_atlas_coords(E->get().tile); - cell.alternative_tile = E->get().alternative; + cell.set_atlas_coords(E.tile); + cell.alternative_tile = E.alternative; current_tile_data_editor->draw_over_tile(alternative_tiles_control_unscaled, xform, cell, true); } } @@ -2031,6 +2093,13 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { } void TileSetAtlasSourceEditor::_tile_set_changed() { + if (tile_set->get_source_count() == 0) { + // No sources, so nothing to do here anymore. + tile_set->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); + tile_set = Ref<TileSet>(); + return; + } + tile_set_changed_needs_update = true; } @@ -2048,14 +2117,16 @@ void TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed(String p_what) } void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { - UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); - ERR_FAIL_COND(!undo_redo); + EditorUndoRedoManager *undo_redo_man = Object::cast_to<EditorUndoRedoManager>(p_undo_redo); + ERR_FAIL_NULL(undo_redo_man); -#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); +#define ADD_UNDO(obj, property) undo_redo_man->add_undo_property(obj, property, obj->get(property)); - undo_redo->start_force_keep_in_merge_ends(); AtlasTileProxyObject *tile_data_proxy = Object::cast_to<AtlasTileProxyObject>(p_edited); if (tile_data_proxy) { + UndoRedo *internal_undo_redo = undo_redo_man->get_history_for_object(tile_data_proxy).undo_redo; + internal_undo_redo->start_force_keep_in_merge_ends(); + Vector<String> components = String(p_property).split("/", true, 2); if (components.size() == 2 && components[1] == "polygons_count") { int layer_index = components[0].trim_prefix("physics_layer_").to_int(); @@ -2070,13 +2141,15 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo } } else if (p_property == "terrain_set") { int current_terrain_set = tile_data_proxy->get("terrain_set"); + ADD_UNDO(tile_data_proxy, "terrain"); for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(current_terrain_set, bit)) { + if (tile_set->is_valid_terrain_peering_bit(current_terrain_set, bit)) { ADD_UNDO(tile_data_proxy, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); } } } + internal_undo_redo->end_force_keep_in_merge_ends(); } TileSetAtlasSourceProxyObject *atlas_source_proxy = Object::cast_to<TileSetAtlasSourceProxyObject>(p_edited); @@ -2084,6 +2157,9 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo TileSetAtlasSource *atlas_source = atlas_source_proxy->get_edited(); ERR_FAIL_COND(!atlas_source); + UndoRedo *internal_undo_redo = undo_redo_man->get_history_for_object(atlas_source).undo_redo; + internal_undo_redo->start_force_keep_in_merge_ends(); + PackedVector2Array arr; if (p_property == "texture") { arr = atlas_source->get_tiles_to_be_removed_on_change(p_new_value, atlas_source->get_margins(), atlas_source->get_separation(), atlas_source->get_texture_region_size()); @@ -2110,8 +2186,8 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo } } } + internal_undo_redo->end_force_keep_in_merge_ends(); } - undo_redo->end_force_keep_in_merge_ends(); #undef ADD_UNDO } @@ -2122,7 +2198,12 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource ERR_FAIL_COND(p_source_id < 0); ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source); - if (p_tile_set == tile_set && p_tile_set_atlas_source == tile_set_atlas_source && p_source_id == tile_set_atlas_source_id) { + bool new_read_only_state = false; + if (p_tile_set.is_valid()) { + new_read_only_state = EditorNode::get_singleton()->is_resource_read_only(p_tile_set); + } + + if (p_tile_set == tile_set && p_tile_set_atlas_source == tile_set_atlas_source && p_source_id == tile_set_atlas_source_id && new_read_only_state == read_only) { return; } @@ -2139,11 +2220,23 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource tile_set_atlas_source = p_tile_set_atlas_source; tile_set_atlas_source_id = p_source_id; - // Add the listener again. + // Read-only is off by default. + read_only = new_read_only_state; + if (tile_set.is_valid()) { tile_set->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } + if (read_only && tools_button_group->get_pressed_button() == tool_paint_button) { + tool_paint_button->set_pressed(false); + tool_setup_atlas_source_button->set_pressed(true); + } + + // Disable buttons in read-only mode. + tool_paint_button->set_disabled(read_only); + tools_settings_erase_button->set_disabled(read_only); + tool_advanced_menu_button->set_disabled(read_only); + // Update everything. _update_source_inspector(); @@ -2172,6 +2265,7 @@ void TileSetAtlasSourceEditor::_auto_create_tiles() { Vector2i separation = tile_set_atlas_source->get_separation(); Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size(); Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Create tiles in non-transparent texture regions")); for (int y = 0; y < grid_size.y; y++) { for (int x = 0; x < grid_size.x; x++) { @@ -2217,11 +2311,12 @@ void TileSetAtlasSourceEditor::_auto_remove_tiles() { Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size(); Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove tiles in fully transparent texture regions")); List<PropertyInfo> list; tile_set_atlas_source->get_property_list(&list); - Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); + HashMap<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { Vector2i coords = tile_set_atlas_source->get_tile_id(i); @@ -2269,20 +2364,33 @@ void TileSetAtlasSourceEditor::_auto_remove_tiles() { void TileSetAtlasSourceEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { tool_setup_atlas_source_button->set_icon(get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); tool_select_button->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); tool_paint_button->set_icon(get_theme_icon(SNAME("CanvasItem"), SNAME("EditorIcons"))); tools_settings_erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons"))); - tool_advanced_menu_buttom->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + tool_advanced_menu_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); resize_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); resize_handle_disabled = get_theme_icon(SNAME("EditorHandleDisabled"), SNAME("EditorIcons")); - break; - case NOTIFICATION_INTERNAL_PROCESS: + } break; + + case NOTIFICATION_INTERNAL_PROCESS: { if (tile_set_changed_needs_update) { + // Read-only is off by default + read_only = false; + // Add the listener again and check for read-only status. + if (tile_set.is_valid()) { + read_only = EditorNode::get_singleton()->is_resource_read_only(tile_set); + } + + // Disable buttons in read-only mode. + tool_paint_button->set_disabled(read_only); + tools_settings_erase_button->set_disabled(read_only); + tool_advanced_menu_button->set_disabled(read_only); + // Update everything. _update_source_inspector(); @@ -2297,9 +2405,24 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { tile_set_changed_needs_update = false; } - break; - default: - break; + } break; + + case NOTIFICATION_EXIT_TREE: { + for (KeyValue<String, TileDataEditor *> &E : tile_data_editors) { + Control *toolbar = E.value->get_toolbar(); + if (toolbar->get_parent() == tool_settings_tile_data_toolbar_container) { + tool_settings_tile_data_toolbar_container->remove_child(toolbar); + } + } + } break; + + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/localize_settings")) { + EditorPropertyNameProcessor::Style style = EditorPropertyNameProcessor::get_singleton()->get_settings_style(); + atlas_source_inspector->set_property_name_style(style); + tile_inspector->set_property_name_style(style); + } + } break; } } @@ -2314,58 +2437,96 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { set_process_unhandled_key_input(true); set_process_internal(true); - // -- Right side -- - HSplitContainer *split_container_right_side = memnew(HSplitContainer); - split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL); - add_child(split_container_right_side); - // Middle panel. - ScrollContainer *middle_panel = memnew(ScrollContainer); - middle_panel->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); - middle_panel->set_custom_minimum_size(Size2i(200, 0) * EDSCALE); - split_container_right_side->add_child(middle_panel); - VBoxContainer *middle_vbox_container = memnew(VBoxContainer); - middle_vbox_container->set_h_size_flags(SIZE_EXPAND_FILL); - middle_panel->add_child(middle_vbox_container); + middle_vbox_container->set_custom_minimum_size(Size2(200, 0) * EDSCALE); + add_child(middle_vbox_container); - // Tile inspector. - tile_inspector_label = memnew(Label); - tile_inspector_label->set_text(TTR("Tile Properties:")); - middle_vbox_container->add_child(tile_inspector_label); + // -- Toolbox -- + tools_button_group.instantiate(); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_source_inspector).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_data_editors).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_current_tile_data_editor).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar).unbind(1)); + HBoxContainer *toolbox = memnew(HBoxContainer); + middle_vbox_container->add_child(toolbox); + + tool_setup_atlas_source_button = memnew(Button); + tool_setup_atlas_source_button->set_text(TTR("Setup")); + tool_setup_atlas_source_button->set_flat(true); + tool_setup_atlas_source_button->set_toggle_mode(true); + tool_setup_atlas_source_button->set_pressed(true); + tool_setup_atlas_source_button->set_button_group(tools_button_group); + tool_setup_atlas_source_button->set_tooltip_text(TTR("Atlas setup. Add/Remove tiles tool (use the shift key to create big tiles, control for rectangle editing).")); + toolbox->add_child(tool_setup_atlas_source_button); + + tool_select_button = memnew(Button); + tool_select_button->set_text(TTR("Select")); + tool_select_button->set_flat(true); + tool_select_button->set_toggle_mode(true); + tool_select_button->set_pressed(false); + tool_select_button->set_button_group(tools_button_group); + tool_select_button->set_tooltip_text(TTR("Select tiles.")); + toolbox->add_child(tool_select_button); + + tool_paint_button = memnew(Button); + tool_paint_button->set_text(TTR("Paint")); + tool_paint_button->set_flat(true); + tool_paint_button->set_toggle_mode(true); + tool_paint_button->set_button_group(tools_button_group); + tool_paint_button->set_tooltip_text(TTR("Paint properties.")); + toolbox->add_child(tool_paint_button); + + // Tile inspector. tile_proxy_object = memnew(AtlasTileProxyObject(this)); tile_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_proxy_object_changed)); tile_inspector = memnew(EditorInspector); - tile_inspector->set_undo_redo(undo_redo); - tile_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + tile_inspector->set_v_size_flags(SIZE_EXPAND_FILL); + tile_inspector->set_show_categories(true); tile_inspector->edit(tile_proxy_object); tile_inspector->set_use_folding(true); tile_inspector->connect("property_selected", callable_mp(this, &TileSetAtlasSourceEditor::_inspector_property_selected)); + tile_inspector->set_property_name_style(EditorPropertyNameProcessor::get_singleton()->get_settings_style()); middle_vbox_container->add_child(tile_inspector); tile_inspector_no_tile_selected_label = memnew(Label); + tile_inspector_no_tile_selected_label->set_v_size_flags(SIZE_EXPAND | SIZE_SHRINK_CENTER); tile_inspector_no_tile_selected_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); - tile_inspector_no_tile_selected_label->set_text(TTR("No tile selected.")); + tile_inspector_no_tile_selected_label->set_text(TTR("No tiles selected.")); middle_vbox_container->add_child(tile_inspector_no_tile_selected_label); // Property values palette. + tile_data_editors_scroll = memnew(ScrollContainer); + tile_data_editors_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + tile_data_editors_scroll->set_v_size_flags(SIZE_EXPAND_FILL); + middle_vbox_container->add_child(tile_data_editors_scroll); + + VBoxContainer *tile_data_editors_vbox = memnew(VBoxContainer); + tile_data_editors_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + tile_data_editors_scroll->add_child(tile_data_editors_vbox); + tile_data_editors_popup = memnew(Popup); tile_data_editors_label = memnew(Label); tile_data_editors_label->set_text(TTR("Paint Properties:")); - middle_vbox_container->add_child(tile_data_editors_label); + tile_data_editors_label->set_theme_type_variation("HeaderSmall"); + tile_data_editors_vbox->add_child(tile_data_editors_label); tile_data_editor_dropdown_button = memnew(Button); tile_data_editor_dropdown_button->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_draw)); tile_data_editor_dropdown_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_pressed)); - middle_vbox_container->add_child(tile_data_editor_dropdown_button); + tile_data_editors_vbox->add_child(tile_data_editor_dropdown_button); tile_data_editor_dropdown_button->add_child(tile_data_editors_popup); tile_data_editors_tree = memnew(Tree); tile_data_editors_tree->set_hide_root(true); - tile_data_editors_tree->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + tile_data_editors_tree->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); tile_data_editors_tree->set_h_scroll_enabled(false); tile_data_editors_tree->set_v_scroll_enabled(false); tile_data_editors_tree->connect("item_selected", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editors_tree_selected)); @@ -2373,80 +2534,26 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_data_painting_editor_container = memnew(VBoxContainer); tile_data_painting_editor_container->set_h_size_flags(SIZE_EXPAND_FILL); - middle_vbox_container->add_child(tile_data_painting_editor_container); + tile_data_editors_vbox->add_child(tile_data_painting_editor_container); // Atlas source inspector. - atlas_source_inspector_label = memnew(Label); - atlas_source_inspector_label->set_text(TTR("Atlas Properties:")); - middle_vbox_container->add_child(atlas_source_inspector_label); - atlas_source_proxy_object = memnew(TileSetAtlasSourceProxyObject()); atlas_source_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed)); atlas_source_inspector = memnew(EditorInspector); - atlas_source_inspector->set_undo_redo(undo_redo); - atlas_source_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + atlas_source_inspector->set_v_size_flags(SIZE_EXPAND_FILL); + atlas_source_inspector->set_show_categories(true); atlas_source_inspector->edit(atlas_source_proxy_object); + atlas_source_inspector->set_property_name_style(EditorPropertyNameProcessor::get_singleton()->get_settings_style()); middle_vbox_container->add_child(atlas_source_inspector); - // Right panel. - VBoxContainer *right_panel = memnew(VBoxContainer); - right_panel->set_h_size_flags(SIZE_EXPAND_FILL); - right_panel->set_v_size_flags(SIZE_EXPAND_FILL); - split_container_right_side->add_child(right_panel); - - // -- Dialogs -- - confirm_auto_create_tiles = memnew(AcceptDialog); - confirm_auto_create_tiles->set_title(TTR("Auto Create Tiles in Non-Transparent Texture Regions?")); - confirm_auto_create_tiles->set_text(TTR("The atlas's texture was modified.\nWould you like to automatically create tiles in the atlas?")); - confirm_auto_create_tiles->get_ok_button()->set_text(TTR("Yes")); - confirm_auto_create_tiles->add_cancel_button()->set_text(TTR("No")); - confirm_auto_create_tiles->connect("confirmed", callable_mp(this, &TileSetAtlasSourceEditor::_auto_create_tiles)); - add_child(confirm_auto_create_tiles); - - // -- Toolbox -- - tools_button_group.instantiate(); - tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles).unbind(1)); - tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label).unbind(1)); - tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_source_inspector).unbind(1)); - tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector).unbind(1)); - tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_data_editors).unbind(1)); - tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_current_tile_data_editor).unbind(1)); - tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).unbind(1)); - tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar).unbind(1)); - - toolbox = memnew(HBoxContainer); - right_panel->add_child(toolbox); - - tool_setup_atlas_source_button = memnew(Button); - tool_setup_atlas_source_button->set_flat(true); - tool_setup_atlas_source_button->set_toggle_mode(true); - tool_setup_atlas_source_button->set_pressed(true); - tool_setup_atlas_source_button->set_button_group(tools_button_group); - tool_setup_atlas_source_button->set_tooltip(TTR("Atlas setup. Add/Remove tiles tool (use the shift key to create big tiles, control for rectangle editing).")); - toolbox->add_child(tool_setup_atlas_source_button); - - tool_select_button = memnew(Button); - tool_select_button->set_flat(true); - tool_select_button->set_toggle_mode(true); - tool_select_button->set_pressed(false); - tool_select_button->set_button_group(tools_button_group); - tool_select_button->set_tooltip(TTR("Select tiles.")); - toolbox->add_child(tool_select_button); - - tool_paint_button = memnew(Button); - tool_paint_button->set_flat(true); - tool_paint_button->set_toggle_mode(true); - tool_paint_button->set_button_group(tools_button_group); - tool_paint_button->set_tooltip(TTR("Paint properties.")); - toolbox->add_child(tool_paint_button); + // -- Right side -- + VBoxContainer *right_vbox_container = memnew(VBoxContainer); + add_child(right_vbox_container); // Tool settings. tool_settings = memnew(HBoxContainer); - toolbox->add_child(tool_settings); - - tool_settings_vsep = memnew(VSeparator); - tool_settings->add_child(tool_settings_vsep); + right_vbox_container->add_child(tool_settings); tool_settings_tile_data_toolbar_container = memnew(HBoxContainer); tool_settings->add_child(tool_settings_tile_data_toolbar_container); @@ -2458,29 +2565,36 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tools_settings_erase_button->set_shortcut_context(this); tool_settings->add_child(tools_settings_erase_button); - tool_advanced_menu_buttom = memnew(MenuButton); - tool_advanced_menu_buttom->set_flat(true); - tool_advanced_menu_buttom->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES); - tool_advanced_menu_buttom->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES); - tool_advanced_menu_buttom->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); - toolbox->add_child(tool_advanced_menu_buttom); + tool_advanced_menu_button = memnew(MenuButton); + tool_advanced_menu_button->set_flat(true); + tool_advanced_menu_button->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES); + tool_advanced_menu_button->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES); + tool_advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); + tool_settings->add_child(tool_advanced_menu_button); _update_toolbar(); // Right side of toolbar. Control *middle_space = memnew(Control); middle_space->set_h_size_flags(SIZE_EXPAND_FILL); - toolbox->add_child(middle_space); + tool_settings->add_child(middle_space); tool_tile_id_label = memnew(Label); tool_tile_id_label->set_mouse_filter(Control::MOUSE_FILTER_STOP); - toolbox->add_child(tool_tile_id_label); + tool_settings->add_child(tool_tile_id_label); _update_tile_id_label(); + // Right panel. + VBoxContainer *right_panel = memnew(VBoxContainer); + right_panel->set_h_size_flags(SIZE_EXPAND_FILL); + right_panel->set_v_size_flags(SIZE_EXPAND_FILL); + right_vbox_container->add_child(right_panel); + // Tile atlas view. tile_atlas_view = memnew(TileAtlasView); tile_atlas_view->set_h_size_flags(SIZE_EXPAND_FILL); tile_atlas_view->set_v_size_flags(SIZE_EXPAND_FILL); + tile_atlas_view->set_custom_minimum_size(Size2(200, 0) * EDSCALE); tile_atlas_view->connect("transform_changed", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::set_atlas_view_transform)); tile_atlas_view->connect("transform_changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_view_transform_changed).unbind(2)); right_panel->add_child(tile_atlas_view); @@ -2503,7 +2617,6 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control); tile_atlas_control_unscaled = memnew(Control); - tile_atlas_control_unscaled->set_anchors_and_offsets_preset(Control::PRESET_WIDE); tile_atlas_control_unscaled->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw)); tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control_unscaled, false); tile_atlas_control_unscaled->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); @@ -2520,22 +2633,21 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control); alternative_tiles_control_unscaled = memnew(Control); - alternative_tiles_control_unscaled->set_anchors_and_offsets_preset(Control::PRESET_WIDE); alternative_tiles_control_unscaled->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw)); tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control_unscaled, false); alternative_tiles_control_unscaled->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - tile_atlas_view_missing_source_label = memnew(Label); - tile_atlas_view_missing_source_label->set_text(TTR("Add or select an atlas texture to the left panel.")); - tile_atlas_view_missing_source_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); - tile_atlas_view_missing_source_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); - tile_atlas_view_missing_source_label->set_h_size_flags(SIZE_EXPAND_FILL); - tile_atlas_view_missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL); - tile_atlas_view_missing_source_label->hide(); - right_panel->add_child(tile_atlas_view_missing_source_label); - EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetAtlasSourceEditor::_undo_redo_inspector_callback)); + // -- Dialogs -- + confirm_auto_create_tiles = memnew(AcceptDialog); + confirm_auto_create_tiles->set_title(TTR("Auto Create Tiles in Non-Transparent Texture Regions?")); + confirm_auto_create_tiles->set_text(TTR("The atlas's texture was modified.\nWould you like to automatically create tiles in the atlas?")); + confirm_auto_create_tiles->set_ok_button_text(TTR("Yes")); + confirm_auto_create_tiles->add_cancel_button()->set_text(TTR("No")); + confirm_auto_create_tiles->connect("confirmed", callable_mp(this, &TileSetAtlasSourceEditor::_auto_create_tiles)); + add_child(confirm_auto_create_tiles); + // Inspector plugin. Ref<EditorInspectorPluginTileData> tile_data_inspector_plugin; tile_data_inspector_plugin.instantiate(); @@ -2593,7 +2705,7 @@ void EditorPropertyTilePolygon::_polygons_changed() { changed_properties.push_back(vformat(element_pattern, i)); values.push_back(generic_tile_polygon_editor->get_polygon(i)); } - emit_signal("multiple_properties_changed", changed_properties, values, false); + emit_signal(SNAME("multiple_properties_changed"), changed_properties, values, false); } } } @@ -2609,8 +2721,8 @@ void EditorPropertyTilePolygon::update_property() { // Set the background Vector2i coords = atlas_tile_proxy_object->get_edited_tiles().front()->get().tile; int alternative = atlas_tile_proxy_object->get_edited_tiles().front()->get().alternative; - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); - generic_tile_polygon_editor->set_background(tile_set_atlas_source->get_texture(), tile_set_atlas_source->get_tile_texture_region(coords), tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); + TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative); + generic_tile_polygon_editor->set_background(tile_set_atlas_source->get_texture(), tile_set_atlas_source->get_tile_texture_region(coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); // Reset the polygons. generic_tile_polygon_editor->clear_polygons(); @@ -2679,7 +2791,7 @@ bool EditorInspectorPluginTileData::can_handle(Object *p_object) { return Object::cast_to<TileSetAtlasSourceEditor::AtlasTileProxyObject>(p_object) != nullptr; } -bool EditorInspectorPluginTileData::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { +bool EditorInspectorPluginTileData::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) { Vector<String> components = String(p_path).split("/", true, 2); if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { // Occlusion layers. diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index 51771c59ba..5141824f79 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* tile_set_atlas_source_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_set_atlas_source_editor.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 TILE_SET_ATLAS_SOURCE_EDITOR_H #define TILE_SET_ATLAS_SOURCE_EDITOR_H @@ -34,14 +34,16 @@ #include "tile_atlas_view.h" #include "tile_data_editors.h" -#include "editor/editor_node.h" #include "scene/gui/split_container.h" #include "scene/resources/tile_set.h" +class Popup; class TileSet; +class Tree; +class VSeparator; -class TileSetAtlasSourceEditor : public HBoxContainer { - GDCLASS(TileSetAtlasSourceEditor, HBoxContainer); +class TileSetAtlasSourceEditor : public HSplitContainer { + GDCLASS(TileSetAtlasSourceEditor, HSplitContainer); public: // A class to store which tiles are selected. @@ -75,7 +77,7 @@ public: public: void set_id(int p_id); - int get_id(); + int get_id() const; void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id); TileSetAtlasSource *get_edited() { return tile_set_atlas_source; }; @@ -86,10 +88,10 @@ public: GDCLASS(AtlasTileProxyObject, Object); private: - TileSetAtlasSourceEditor *tiles_set_atlas_source_editor; + TileSetAtlasSourceEditor *tiles_set_atlas_source_editor = nullptr; TileSetAtlasSource *tile_set_atlas_source = nullptr; - Set<TileSelection> tiles = Set<TileSelection>(); + RBSet<TileSelection> tiles = RBSet<TileSelection>(); protected: bool _set(const StringName &p_name, const Variant &p_value); @@ -100,10 +102,10 @@ public: public: TileSetAtlasSource *get_edited_tile_set_atlas_source() const { return tile_set_atlas_source; }; - Set<TileSelection> get_edited_tiles() const { return tiles; }; + RBSet<TileSelection> get_edited_tiles() const { return tiles; }; // Update the proxyed object. - void edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles = Set<TileSelection>()); + void edit(TileSetAtlasSource *p_tile_set_atlas_source, RBSet<TileSelection> p_tiles = RBSet<TileSelection>()); AtlasTileProxyObject(TileSetAtlasSourceEditor *p_tiles_set_atlas_source_editor) { tiles_set_atlas_source_editor = p_tiles_set_atlas_source_editor; @@ -111,46 +113,43 @@ public: }; private: + bool read_only = false; + Ref<TileSet> tile_set; TileSetAtlasSource *tile_set_atlas_source = nullptr; int tile_set_atlas_source_id = TileSet::INVALID_SOURCE; - UndoRedo *undo_redo = EditorNode::get_undo_redo(); - bool tile_set_changed_needs_update = false; // -- Properties painting -- - VBoxContainer *tile_data_painting_editor_container; - Label *tile_data_editors_label; - Button *tile_data_editor_dropdown_button; - Popup *tile_data_editors_popup; - Tree *tile_data_editors_tree; + ScrollContainer *tile_data_editors_scroll = nullptr; + VBoxContainer *tile_data_painting_editor_container = nullptr; + Label *tile_data_editors_label = nullptr; + Button *tile_data_editor_dropdown_button = nullptr; + Popup *tile_data_editors_popup = nullptr; + Tree *tile_data_editors_tree = nullptr; void _tile_data_editor_dropdown_button_draw(); void _tile_data_editor_dropdown_button_pressed(); // -- Tile data editors -- String current_property; Control *current_tile_data_editor_toolbar = nullptr; - Map<String, TileDataEditor *> tile_data_editors; + HashMap<String, TileDataEditor *> tile_data_editors; TileDataEditor *current_tile_data_editor = nullptr; void _tile_data_editors_tree_selected(); // -- Inspector -- - AtlasTileProxyObject *tile_proxy_object; - Label *tile_inspector_label; - EditorInspector *tile_inspector; - Label *tile_inspector_no_tile_selected_label; + AtlasTileProxyObject *tile_proxy_object = nullptr; + EditorInspector *tile_inspector = nullptr; + Label *tile_inspector_no_tile_selected_label = nullptr; String selected_property; void _inspector_property_selected(String p_property); - TileSetAtlasSourceProxyObject *atlas_source_proxy_object; - Label *atlas_source_inspector_label; - EditorInspector *atlas_source_inspector; + TileSetAtlasSourceProxyObject *atlas_source_proxy_object = nullptr; + EditorInspector *atlas_source_inspector = nullptr; // -- Atlas view -- - HBoxContainer *toolbox; - Label *tile_atlas_view_missing_source_label; - TileAtlasView *tile_atlas_view; + TileAtlasView *tile_atlas_view = nullptr; // Dragging enum DragType { @@ -183,10 +182,10 @@ private: Vector2i drag_current_tile; Rect2i drag_start_tile_shape; - Set<Vector2i> drag_modified_tiles; + RBSet<Vector2i> drag_modified_tiles; void _end_dragging(); - Map<Vector2i, List<const PropertyInfo *>> _group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas); + HashMap<Vector2i, List<const PropertyInfo *>> _group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas); // Popup functions. enum MenuOptions { @@ -203,20 +202,19 @@ private: // Tool buttons. Ref<ButtonGroup> tools_button_group; - Button *tool_setup_atlas_source_button; - Button *tool_select_button; - Button *tool_paint_button; - Label *tool_tile_id_label; + Button *tool_setup_atlas_source_button = nullptr; + Button *tool_select_button = nullptr; + Button *tool_paint_button = nullptr; + Label *tool_tile_id_label = nullptr; // Tool settings. - HBoxContainer *tool_settings; - VSeparator *tool_settings_vsep; - HBoxContainer *tool_settings_tile_data_toolbar_container; - Button *tools_settings_erase_button; - MenuButton *tool_advanced_menu_buttom; + HBoxContainer *tool_settings = nullptr; + HBoxContainer *tool_settings_tile_data_toolbar_container = nullptr; + Button *tools_settings_erase_button = nullptr; + MenuButton *tool_advanced_menu_button = nullptr; // Selection. - Set<TileSelection> selection; + RBSet<TileSelection> selection; void _set_selection_from_array(Array p_selection); Array _get_selection_as_array(); @@ -224,12 +222,12 @@ private: // A control on the tile atlas to draw and handle input events. Vector2i hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS; - PopupMenu *base_tile_popup_menu; - PopupMenu *empty_base_tile_popup_menu; + PopupMenu *base_tile_popup_menu = nullptr; + PopupMenu *empty_base_tile_popup_menu = nullptr; Ref<Texture2D> resize_handle; Ref<Texture2D> resize_handle_disabled; - Control *tile_atlas_control; - Control *tile_atlas_control_unscaled; + Control *tile_atlas_control = nullptr; + Control *tile_atlas_control_unscaled = nullptr; void _tile_atlas_control_draw(); void _tile_atlas_control_unscaled_draw(); void _tile_atlas_control_mouse_exited(); @@ -239,9 +237,9 @@ private: // A control over the alternative tiles. Vector3i hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE); - PopupMenu *alternative_tile_popup_menu; - Control *alternative_tiles_control; - Control *alternative_tiles_control_unscaled; + PopupMenu *alternative_tile_popup_menu = nullptr; + Control *alternative_tiles_control = nullptr; + Control *alternative_tiles_control_unscaled = nullptr; void _tile_alternatives_control_draw(); void _tile_alternatives_control_unscaled_draw(); void _tile_alternatives_control_mouse_exited(); @@ -265,7 +263,7 @@ private: // -- Misc -- void _auto_create_tiles(); void _auto_remove_tiles(); - AcceptDialog *confirm_auto_create_tiles; + AcceptDialog *confirm_auto_create_tiles = nullptr; void _tile_set_changed(); void _tile_proxy_object_changed(String p_what); @@ -281,6 +279,8 @@ public: void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_source, int p_source_id); void init_source(); + virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override; + TileSetAtlasSourceEditor(); ~TileSetAtlasSourceEditor(); }; @@ -294,7 +294,7 @@ class EditorPropertyTilePolygon : public EditorProperty { void _add_focusable_children(Node *p_node); - GenericTilePolygonEditor *generic_tile_polygon_editor; + GenericTilePolygonEditor *generic_tile_polygon_editor = nullptr; void _polygons_changed(); public: @@ -312,7 +312,7 @@ class EditorInspectorPluginTileData : public EditorInspectorPlugin { public: virtual bool can_handle(Object *p_object) override; - virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false) override; }; #endif // TILE_SET_ATLAS_SOURCE_EDITOR_H diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index ef8d423724..358cc47977 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -1,39 +1,43 @@ -/*************************************************************************/ -/* tile_set_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_set_editor.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 "tile_set_editor.h" #include "tile_data_editors.h" #include "tiles_editor_plugin.h" +#include "editor/editor_file_system.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "scene/gui/box_container.h" #include "scene/gui/control.h" @@ -63,6 +67,7 @@ void TileSetEditor::_drop_data_fw(const Point2 &p_point, const Variant &p_data, // Actually create the new source. Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource); atlas_source->set_texture(resource); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add a new atlas source")); undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id); undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size()); @@ -84,6 +89,10 @@ void TileSetEditor::_drop_data_fw(const Point2 &p_point, const Variant &p_data, bool TileSetEditor::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { ERR_FAIL_COND_V(!tile_set.is_valid(), false); + if (read_only) { + return false; + } + if (p_from == sources_list) { Dictionary d = p_data; @@ -115,7 +124,9 @@ bool TileSetEditor::_can_drop_data_fw(const Point2 &p_point, const Variant &p_da } void TileSetEditor::_update_sources_list(int force_selected_id) { - ERR_FAIL_COND(!tile_set.is_valid()); + if (tile_set.is_null()) { + return; + } // Get the previously selected id. int old_selected = TileSet::INVALID_SOURCE; @@ -137,9 +148,8 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { sources_list->clear(); // Update the atlas sources. - for (int i = 0; i < tile_set->get_source_count(); i++) { - int source_id = tile_set->get_source_id(i); - + List<int> source_ids = TilesEditorPlugin::get_singleton()->get_sorted_sources(tile_set); + for (const int &source_id : source_ids) { TileSetSource *source = *tile_set->get_source(source_id); Ref<Texture2D> texture; @@ -147,7 +157,7 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { // Common to all type of sources. if (!source->get_name().is_empty()) { - item_text = vformat(TTR("%s (id:%d)"), source->get_name(), source_id); + item_text = vformat(TTR("%s (ID: %d)"), source->get_name(), source_id); } // Atlas source. @@ -156,9 +166,9 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { texture = atlas_source->get_texture(); if (item_text.is_empty()) { if (texture.is_valid()) { - item_text = vformat("%s (ID:%d)", texture->get_path().get_file(), source_id); + item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id); } else { - item_text = vformat(TTR("No Texture Atlas Source (ID:%d)"), source_id); + item_text = vformat(TTR("No Texture Atlas Source (ID: %d)"), source_id); } } } @@ -168,20 +178,20 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { if (scene_collection_source) { texture = get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")); if (item_text.is_empty()) { - item_text = vformat(TTR("Scene Collection Source (ID:%d)"), source_id); + item_text = vformat(TTR("Scene Collection Source (ID: %d)"), source_id); } } // Use default if not valid. if (item_text.is_empty()) { - item_text = vformat(TTR("Unknown Type Source (ID:%d)"), source_id); + item_text = vformat(TTR("Unknown Type Source (ID: %d)"), source_id); } if (!texture.is_valid()) { texture = missing_texture_texture; } sources_list->add_item(item_text, texture); - sources_list->set_item_metadata(i, source_id); + sources_list->set_item_metadata(-1, source_id); } // Set again the current selected item if needed. @@ -189,6 +199,7 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { for (int i = 0; i < sources_list->get_item_count(); i++) { if ((int)sources_list->get_item_metadata(i) == to_select) { sources_list->set_current(i); + sources_list->ensure_current_is_visible(); if (old_selected != to_select) { sources_list->emit_signal(SNAME("item_selected"), sources_list->get_current()); } @@ -216,7 +227,7 @@ void TileSetEditor::_source_selected(int p_source_index) { ERR_FAIL_COND(!tile_set.is_valid()); // Update the selected source. - sources_delete_button->set_disabled(p_source_index < 0); + sources_delete_button->set_disabled(p_source_index < 0 || read_only); if (p_source_index >= 0) { int source_id = sources_list->get_item_metadata(p_source_index); @@ -253,6 +264,7 @@ void TileSetEditor::_source_delete_pressed() { Ref<TileSetSource> source = tile_set->get_source(to_delete); // Remove the source. + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove source")); undo_redo->add_do_method(*tile_set, "remove_source", to_delete); undo_redo->add_undo_method(*tile_set, "add_source", source, to_delete); @@ -271,6 +283,7 @@ void TileSetEditor::_source_add_id_pressed(int p_id_pressed) { Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource); // Add a new source. + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add atlas source")); undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id); undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size()); @@ -285,6 +298,7 @@ void TileSetEditor::_source_add_id_pressed(int p_id_pressed) { Ref<TileSetScenesCollectionSource> scene_collection_source = memnew(TileSetScenesCollectionSource); // Add a new source. + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add atlas source")); undo_redo->add_do_method(*tile_set, "add_source", scene_collection_source, source_id); undo_redo->add_undo_method(*tile_set, "remove_source", source_id); @@ -312,35 +326,69 @@ void TileSetEditor::_sources_advanced_menu_id_pressed(int p_id_pressed) { } } +void TileSetEditor::_set_source_sort(int p_sort) { + TilesEditorPlugin::get_singleton()->set_sorting_option(p_sort); + for (int i = 0; i != TilesEditorPlugin::SOURCE_SORT_MAX; i++) { + source_sort_button->get_popup()->set_item_checked(i, (i == (int)p_sort)); + } + + int old_selected = TileSet::INVALID_SOURCE; + if (sources_list->get_current() >= 0) { + int source_id = sources_list->get_item_metadata(sources_list->get_current()); + if (tile_set->has_source(source_id)) { + old_selected = source_id; + } + } + _update_sources_list(old_selected); + EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "tile_source_sort", p_sort); +} + void TileSetEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { sources_delete_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); sources_add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + source_sort_button->set_icon(get_theme_icon(SNAME("Sort"), SNAME("EditorIcons"))); sources_advanced_menu_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); missing_texture_texture = get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons")); - break; - case NOTIFICATION_INTERNAL_PROCESS: + _update_sources_list(); + } break; + + case NOTIFICATION_INTERNAL_PROCESS: { if (tile_set_changed_needs_update) { if (tile_set.is_valid()) { tile_set->set_edited(true); } + + read_only = false; + if (tile_set.is_valid()) { + read_only = EditorNode::get_singleton()->is_resource_read_only(tile_set); + } + _update_sources_list(); _update_patterns_list(); + + sources_add_button->set_disabled(read_only); + sources_advanced_menu_button->set_disabled(read_only); + source_sort_button->set_disabled(read_only); + tile_set_changed_needs_update = false; } - break; - default: - break; + } break; } } void TileSetEditor::_patterns_item_list_gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(!tile_set.is_valid()); + if (EditorNode::get_singleton()->is_resource_read_only(tile_set)) { + return; + } + if (ED_IS_SHORTCUT("tiles_editor/delete", p_event) && p_event->is_pressed() && !p_event->is_echo()) { Vector<int> selected = patterns_item_list->get_selected_items(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove TileSet patterns")); for (int i = 0; i < selected.size(); i++) { int pattern_index = selected[i]; @@ -387,11 +435,11 @@ void TileSetEditor::_tab_changed(int p_tab_changed) { } void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) { - UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); - ERR_FAIL_COND(!undo_redo); + EditorUndoRedoManager *undo_redo_man = Object::cast_to<EditorUndoRedoManager>(p_undo_redo); + ERR_FAIL_NULL(undo_redo_man); - TileSet *tile_set = Object::cast_to<TileSet>(p_edited); - if (!tile_set) { + TileSet *ed_tile_set = Object::cast_to<TileSet>(p_edited); + if (!ed_tile_set) { return; } @@ -401,18 +449,18 @@ void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_ int begin = 0; int end; if (p_array_prefix == "occlusion_layer_") { - end = tile_set->get_occlusion_layers_count(); + end = ed_tile_set->get_occlusion_layers_count(); } else if (p_array_prefix == "physics_layer_") { - end = tile_set->get_physics_layers_count(); + end = ed_tile_set->get_physics_layers_count(); } else if (p_array_prefix == "terrain_set_") { - end = tile_set->get_terrain_sets_count(); + end = ed_tile_set->get_terrain_sets_count(); } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { int terrain_set = components[0].trim_prefix("terrain_set_").to_int(); - end = tile_set->get_terrains_count(terrain_set); + end = ed_tile_set->get_terrains_count(terrain_set); } else if (p_array_prefix == "navigation_layer_") { - end = tile_set->get_navigation_layers_count(); + end = ed_tile_set->get_navigation_layers_count(); } else if (p_array_prefix == "custom_data_layer_") { - end = tile_set->get_custom_data_layers_count(); + end = ed_tile_set->get_custom_data_layers_count(); } else { ERR_FAIL_MSG("Invalid array prefix for TileSet."); } @@ -432,16 +480,45 @@ void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_ end = MIN(MAX(p_from_index, p_to_pos) + 1, end); } -#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); +#define ADD_UNDO(obj, property) undo_redo_man->add_undo_property(obj, property, obj->get(property)); + + // Add undo method to adding array element. + if (p_array_prefix == "occlusion_layer_") { + if (p_from_index < 0) { + undo_redo_man->add_undo_method(ed_tile_set, "remove_occlusion_layer", p_to_pos < 0 ? ed_tile_set->get_occlusion_layers_count() : p_to_pos); + } + } else if (p_array_prefix == "physics_layer_") { + if (p_from_index < 0) { + undo_redo_man->add_undo_method(ed_tile_set, "remove_physics_layer", p_to_pos < 0 ? ed_tile_set->get_physics_layers_count() : p_to_pos); + } + } else if (p_array_prefix == "terrain_set_") { + if (p_from_index < 0) { + undo_redo_man->add_undo_method(ed_tile_set, "remove_terrain_set", p_to_pos < 0 ? ed_tile_set->get_terrain_sets_count() : p_to_pos); + } + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { + int terrain_set = components[0].trim_prefix("terrain_set_").to_int(); + if (p_from_index < 0) { + undo_redo_man->add_undo_method(ed_tile_set, "remove_terrain", terrain_set, p_to_pos < 0 ? ed_tile_set->get_terrains_count(terrain_set) : p_to_pos); + } + } else if (p_array_prefix == "navigation_layer_") { + if (p_from_index < 0) { + undo_redo_man->add_undo_method(ed_tile_set, "remove_navigation_layer", p_to_pos < 0 ? ed_tile_set->get_navigation_layers_count() : p_to_pos); + } + } else if (p_array_prefix == "custom_data_layer_") { + if (p_from_index < 0) { + undo_redo_man->add_undo_method(ed_tile_set, "remove_custom_data_layer", p_to_pos < 0 ? ed_tile_set->get_custom_data_layers_count() : p_to_pos); + } + } + // Save layers' properties. List<PropertyInfo> properties; - tile_set->get_property_list(&properties); + ed_tile_set->get_property_list(&properties); for (PropertyInfo pi : properties) { if (pi.name.begins_with(p_array_prefix)) { String str = pi.name.trim_prefix(p_array_prefix); int to_char_index = 0; while (to_char_index < str.length()) { - if (str[to_char_index] < '0' || str[to_char_index] > '9') { + if (!is_digit(str[to_char_index])) { break; } to_char_index++; @@ -449,23 +526,23 @@ void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_ if (to_char_index > 0) { int array_index = str.left(to_char_index).to_int(); if (array_index >= begin && array_index < end) { - ADD_UNDO(tile_set, pi.name); + ADD_UNDO(ed_tile_set, pi.name); } } } } // Save properties for TileSetAtlasSources tile data - for (int i = 0; i < tile_set->get_source_count(); i++) { - int source_id = tile_set->get_source_id(i); + for (int i = 0; i < ed_tile_set->get_source_count(); i++) { + int source_id = ed_tile_set->get_source_id(i); - Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id); + Ref<TileSetAtlasSource> tas = ed_tile_set->get_source(source_id); if (tas.is_valid()) { for (int j = 0; j < tas->get_tiles_count(); j++) { Vector2i tile_id = tas->get_tile_id(j); for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) { int alternative_id = tas->get_alternative_tile_id(tile_id, k); - TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id)); + TileData *tile_data = tas->get_tile_data(tile_id, alternative_id); ERR_FAIL_COND(!tile_data); // Actually saving stuff. @@ -487,7 +564,7 @@ void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_ for (int terrain_set_index = begin; terrain_set_index < end; terrain_set_index++) { for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(l); - if (tile_data->is_valid_peering_bit_terrain(bit)) { + if (tile_data->is_valid_terrain_peering_bit(bit)) { ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l])); } } @@ -495,7 +572,7 @@ void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_ } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { for (int terrain_index = 0; terrain_index < TileSet::CELL_NEIGHBOR_MAX; terrain_index++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(terrain_index); - if (tile_data->is_valid_peering_bit_terrain(bit)) { + if (tile_data->is_valid_terrain_peering_bit(bit)) { ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[terrain_index])); } } @@ -514,84 +591,85 @@ void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_ } #undef ADD_UNDO - // Add do method. + // Add do method to add/remove array element. if (p_array_prefix == "occlusion_layer_") { if (p_from_index < 0) { - undo_redo->add_do_method(tile_set, "add_occlusion_layer", p_to_pos); + undo_redo_man->add_do_method(ed_tile_set, "add_occlusion_layer", p_to_pos); } else if (p_to_pos < 0) { - undo_redo->add_do_method(tile_set, "remove_occlusion_layer", p_from_index); + undo_redo_man->add_do_method(ed_tile_set, "remove_occlusion_layer", p_from_index); } else { - undo_redo->add_do_method(tile_set, "move_occlusion_layer", p_from_index, p_to_pos); + undo_redo_man->add_do_method(ed_tile_set, "move_occlusion_layer", p_from_index, p_to_pos); } } else if (p_array_prefix == "physics_layer_") { if (p_from_index < 0) { - undo_redo->add_do_method(tile_set, "add_physics_layer", p_to_pos); + undo_redo_man->add_do_method(ed_tile_set, "add_physics_layer", p_to_pos); } else if (p_to_pos < 0) { - undo_redo->add_do_method(tile_set, "remove_physics_layer", p_from_index); + undo_redo_man->add_do_method(ed_tile_set, "remove_physics_layer", p_from_index); } else { - undo_redo->add_do_method(tile_set, "move_physics_layer", p_from_index, p_to_pos); + undo_redo_man->add_do_method(ed_tile_set, "move_physics_layer", p_from_index, p_to_pos); } } else if (p_array_prefix == "terrain_set_") { if (p_from_index < 0) { - undo_redo->add_do_method(tile_set, "add_terrain_set", p_to_pos); + undo_redo_man->add_do_method(ed_tile_set, "add_terrain_set", p_to_pos); } else if (p_to_pos < 0) { - undo_redo->add_do_method(tile_set, "remove_terrain_set", p_from_index); + undo_redo_man->add_do_method(ed_tile_set, "remove_terrain_set", p_from_index); } else { - undo_redo->add_do_method(tile_set, "move_terrain_set", p_from_index, p_to_pos); + undo_redo_man->add_do_method(ed_tile_set, "move_terrain_set", p_from_index, p_to_pos); } } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { int terrain_set = components[0].trim_prefix("terrain_set_").to_int(); if (p_from_index < 0) { - undo_redo->add_do_method(tile_set, "add_terrain", terrain_set, p_to_pos); + undo_redo_man->add_do_method(ed_tile_set, "add_terrain", terrain_set, p_to_pos); } else if (p_to_pos < 0) { - undo_redo->add_do_method(tile_set, "remove_terrain", terrain_set, p_from_index); + undo_redo_man->add_do_method(ed_tile_set, "remove_terrain", terrain_set, p_from_index); } else { - undo_redo->add_do_method(tile_set, "move_terrain", terrain_set, p_from_index, p_to_pos); + undo_redo_man->add_do_method(ed_tile_set, "move_terrain", terrain_set, p_from_index, p_to_pos); } } else if (p_array_prefix == "navigation_layer_") { if (p_from_index < 0) { - undo_redo->add_do_method(tile_set, "add_navigation_layer", p_to_pos); + undo_redo_man->add_do_method(ed_tile_set, "add_navigation_layer", p_to_pos); } else if (p_to_pos < 0) { - undo_redo->add_do_method(tile_set, "remove_navigation_layer", p_from_index); + undo_redo_man->add_do_method(ed_tile_set, "remove_navigation_layer", p_from_index); } else { - undo_redo->add_do_method(tile_set, "move_navigation_layer", p_from_index, p_to_pos); + undo_redo_man->add_do_method(ed_tile_set, "move_navigation_layer", p_from_index, p_to_pos); } } else if (p_array_prefix == "custom_data_layer_") { if (p_from_index < 0) { - undo_redo->add_do_method(tile_set, "add_custom_data_layer", p_to_pos); + undo_redo_man->add_do_method(ed_tile_set, "add_custom_data_layer", p_to_pos); } else if (p_to_pos < 0) { - undo_redo->add_do_method(tile_set, "remove_custom_data_layer", p_from_index); + undo_redo_man->add_do_method(ed_tile_set, "remove_custom_data_layer", p_from_index); } else { - undo_redo->add_do_method(tile_set, "move_custom_data_layer", p_from_index, p_to_pos); + undo_redo_man->add_do_method(ed_tile_set, "move_custom_data_layer", p_from_index, p_to_pos); } } } void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { - UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); - ERR_FAIL_COND(!undo_redo); + EditorUndoRedoManager *undo_redo_man = Object::cast_to<EditorUndoRedoManager>(p_undo_redo); + ERR_FAIL_NULL(undo_redo_man); -#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); - TileSet *tile_set = Object::cast_to<TileSet>(p_edited); - if (tile_set) { +#define ADD_UNDO(obj, property) undo_redo_man->add_undo_property(obj, property, obj->get(property)); + TileSet *ed_tile_set = Object::cast_to<TileSet>(p_edited); + if (ed_tile_set) { Vector<String> components = p_property.split("/", true, 3); - for (int i = 0; i < tile_set->get_source_count(); i++) { - int source_id = tile_set->get_source_id(i); + for (int i = 0; i < ed_tile_set->get_source_count(); i++) { + int source_id = ed_tile_set->get_source_id(i); - Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id); + Ref<TileSetAtlasSource> tas = ed_tile_set->get_source(source_id); if (tas.is_valid()) { for (int j = 0; j < tas->get_tiles_count(); j++) { Vector2i tile_id = tas->get_tile_id(j); for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) { int alternative_id = tas->get_alternative_tile_id(tile_id, k); - TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id)); + TileData *tile_data = tas->get_tile_data(tile_id, alternative_id); ERR_FAIL_COND(!tile_data); if (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") { ADD_UNDO(tile_data, "terrain_set"); + ADD_UNDO(tile_data, "terrain"); for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(l); - if (tile_data->is_valid_peering_bit_terrain(bit)) { + if (tile_data->is_valid_terrain_peering_bit(bit)) { ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l])); } } @@ -607,13 +685,13 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p #undef ADD_UNDO } -void TileSetEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &TileSetEditor::_can_drop_data_fw); - ClassDB::bind_method(D_METHOD("_drop_data_fw"), &TileSetEditor::_drop_data_fw); -} - void TileSetEditor::edit(Ref<TileSet> p_tile_set) { - if (p_tile_set == tile_set) { + bool new_read_only_state = false; + if (p_tile_set.is_valid()) { + new_read_only_state = EditorNode::get_singleton()->is_resource_read_only(p_tile_set); + } + + if (p_tile_set == tile_set && new_read_only_state == read_only) { return; } @@ -625,16 +703,24 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) { // Change the edited object. tile_set = p_tile_set; - // Add the listener again. + // Read-only status is false by default + read_only = new_read_only_state; + + // Add the listener again and check for read-only status. if (tile_set.is_valid()) { + sources_add_button->set_disabled(read_only); + sources_advanced_menu_button->set_disabled(read_only); + source_sort_button->set_disabled(read_only); + tile_set->connect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed)); - _update_sources_list(); + if (first_edit) { + first_edit = false; + _set_source_sort(EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "tile_source_sort", 0)); + } else { + _update_sources_list(); + } _update_patterns_list(); } - - tile_set_atlas_source_editor->hide(); - tile_set_scenes_collection_source_editor->hide(); - no_source_selected_label->show(); } TileSetEditor::TileSetEditor() { @@ -644,6 +730,7 @@ TileSetEditor::TileSetEditor() { // TabBar. tabs_bar = memnew(TabBar); + tabs_bar->set_tab_alignment(TabBar::ALIGNMENT_CENTER); tabs_bar->set_clip_tabs(false); tabs_bar->add_tab(TTR("Tiles")); tabs_bar->add_tab(TTR("Patterns")); @@ -667,18 +754,32 @@ TileSetEditor::TileSetEditor() { split_container_left_side->set_h_size_flags(SIZE_EXPAND_FILL); split_container_left_side->set_v_size_flags(SIZE_EXPAND_FILL); split_container_left_side->set_stretch_ratio(0.25); - split_container_left_side->set_custom_minimum_size(Size2i(70, 0) * EDSCALE); + split_container_left_side->set_custom_minimum_size(Size2(70, 0) * EDSCALE); split_container->add_child(split_container_left_side); + source_sort_button = memnew(MenuButton); + source_sort_button->set_flat(true); + source_sort_button->set_tooltip_text(TTR("Sort Sources")); + + PopupMenu *p = source_sort_button->get_popup(); + p->connect("id_pressed", callable_mp(this, &TileSetEditor::_set_source_sort)); + p->add_radio_check_item(TTR("Sort by ID (Ascending)"), TilesEditorPlugin::SOURCE_SORT_ID); + p->add_radio_check_item(TTR("Sort by ID (Descending)"), TilesEditorPlugin::SOURCE_SORT_ID_REVERSE); + p->add_radio_check_item(TTR("Sort by Name (Ascending)"), TilesEditorPlugin::SOURCE_SORT_NAME); + p->add_radio_check_item(TTR("Sort by Name (Descending)"), TilesEditorPlugin::SOURCE_SORT_NAME_REVERSE); + p->set_item_checked(TilesEditorPlugin::SOURCE_SORT_ID, true); + sources_list = memnew(ItemList); - sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE); + sources_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE); sources_list->set_h_size_flags(SIZE_EXPAND_FILL); sources_list->set_v_size_flags(SIZE_EXPAND_FILL); sources_list->connect("item_selected", callable_mp(this, &TileSetEditor::_source_selected)); sources_list->connect("item_selected", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::set_sources_lists_current)); - sources_list->connect("visibility_changed", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::synchronize_sources_list), varray(sources_list)); + sources_list->connect("visibility_changed", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::synchronize_sources_list).bind(sources_list, source_sort_button)); + sources_list->add_user_signal(MethodInfo("sort_request")); + sources_list->connect("sort_request", callable_mp(this, &TileSetEditor::_update_sources_list).bind(-1)); sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); - sources_list->set_drag_forwarding(this); + SET_DRAG_FORWARDING_CDU(sources_list, TileSetEditor); split_container_left_side->add_child(sources_list); HBoxContainer *sources_bottom_actions = memnew(HBoxContainer); @@ -704,6 +805,7 @@ TileSetEditor::TileSetEditor() { sources_advanced_menu_button->get_popup()->add_item(TTR("Manage Tile Proxies")); sources_advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_sources_advanced_menu_id_pressed)); sources_bottom_actions->add_child(sources_advanced_menu_button); + sources_bottom_actions->add_child(source_sort_button); atlas_merging_dialog = memnew(AtlasMergingDialog); add_child(atlas_merging_dialog); @@ -757,6 +859,7 @@ TileSetEditor::TileSetEditor() { patterns_help_label = memnew(Label); patterns_help_label->set_text(TTR("Add new patterns in the TileMap editing mode.")); + patterns_help_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); patterns_help_label->set_anchors_and_offsets_preset(Control::PRESET_CENTER); patterns_item_list->add_child(patterns_help_label); @@ -764,9 +867,3 @@ TileSetEditor::TileSetEditor() { EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileSet"), callable_mp(this, &TileSetEditor::_move_tile_set_array_element)); EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback)); } - -TileSetEditor::~TileSetEditor() { - if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed)); - } -} diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index 98ebbae02f..d36d3bde41 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -1,38 +1,39 @@ -/*************************************************************************/ -/* tile_set_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_set_editor.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 TILE_SET_EDITOR_H #define TILE_SET_EDITOR_H #include "atlas_merging_dialog.h" #include "scene/gui/box_container.h" +#include "scene/gui/tab_bar.h" #include "scene/resources/tile_set.h" #include "tile_proxies_manager_dialog.h" #include "tile_set_atlas_source_editor.h" @@ -44,20 +45,20 @@ class TileSetEditor : public VBoxContainer { static TileSetEditor *singleton; private: + bool read_only = false; + Ref<TileSet> tile_set; bool tile_set_changed_needs_update = false; - HSplitContainer *split_container; + HSplitContainer *split_container = nullptr; // TabBar. - HBoxContainer *tile_set_toolbar; - TabBar *tabs_bar; + HBoxContainer *tile_set_toolbar = nullptr; + TabBar *tabs_bar = nullptr; // Tiles. - Label *no_source_selected_label; - TileSetAtlasSourceEditor *tile_set_atlas_source_editor; - TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor; - - UndoRedo *undo_redo = EditorNode::get_undo_redo(); + Label *no_source_selected_label = nullptr; + TileSetAtlasSourceEditor *tile_set_atlas_source_editor = nullptr; + TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor = nullptr; void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; @@ -65,22 +66,26 @@ private: void _update_sources_list(int force_selected_id = -1); // Sources management. - Button *sources_delete_button; - MenuButton *sources_add_button; - MenuButton *sources_advanced_menu_button; - ItemList *sources_list; + Button *sources_delete_button = nullptr; + MenuButton *sources_add_button = nullptr; + MenuButton *source_sort_button = nullptr; + MenuButton *sources_advanced_menu_button = nullptr; + ItemList *sources_list = nullptr; Ref<Texture2D> missing_texture_texture; void _source_selected(int p_source_index); void _source_delete_pressed(); void _source_add_id_pressed(int p_id_pressed); void _sources_advanced_menu_id_pressed(int p_id_pressed); + void _set_source_sort(int p_sort); + + AtlasMergingDialog *atlas_merging_dialog = nullptr; + TileProxiesManagerDialog *tile_proxies_manager_dialog = nullptr; - AtlasMergingDialog *atlas_merging_dialog; - TileProxiesManagerDialog *tile_proxies_manager_dialog; + bool first_edit = true; // Patterns. - ItemList *patterns_item_list; - Label *patterns_help_label; + ItemList *patterns_item_list = nullptr; + Label *patterns_help_label = nullptr; void _patterns_item_list_gui_input(const Ref<InputEvent> &p_event); void _pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture); bool select_last_pattern = false; @@ -94,7 +99,6 @@ private: protected: void _notification(int p_what); - static void _bind_methods(); public: _FORCE_INLINE_ static TileSetEditor *get_singleton() { return singleton; } @@ -102,7 +106,6 @@ public: void edit(Ref<TileSet> p_tile_set); TileSetEditor(); - ~TileSetEditor(); }; -#endif // TILE_SET_EDITOR_PLUGIN_H +#endif // TILE_SET_EDITOR_H diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp index 240c017b84..101ec5f66c 100644 --- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp @@ -1,40 +1,45 @@ -/*************************************************************************/ -/* tile_set_scenes_collection_source_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_set_scenes_collection_source_editor.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 "tile_set_scenes_collection_source_editor.h" +#include "editor/editor_file_system.h" +#include "editor/editor_node.h" +#include "editor/editor_property_name_processor.h" #include "editor/editor_resource_preview.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "scene/gui/item_list.h" +#include "scene/gui/split_container.h" #include "core/core_string_names.h" @@ -233,6 +238,7 @@ void TileSetScenesCollectionSourceEditor::_scenes_list_item_activated(int p_inde void TileSetScenesCollectionSourceEditor::_source_add_pressed() { int scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add a Scene Tile")); undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", Ref<PackedScene>(), scene_id); undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id); @@ -247,6 +253,7 @@ void TileSetScenesCollectionSourceEditor::_source_delete_pressed() { ERR_FAIL_COND(selected_indices.size() <= 0); int scene_id = scene_tiles_list->get_item_metadata(selected_indices[0]); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove a Scene Tile")); undo_redo->add_do_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id); undo_redo->add_undo_method(tile_set_scenes_collection_source, "create_scene_tile", tile_set_scenes_collection_source->get_scene_tile_scene(scene_id), scene_id); @@ -278,7 +285,7 @@ void TileSetScenesCollectionSourceEditor::_update_tile_inspector() { void TileSetScenesCollectionSourceEditor::_update_action_buttons() { Vector<int> selected_indices = scene_tiles_list->get_selected_items(); - scene_tile_delete_button->set_disabled(selected_indices.size() <= 0); + scene_tile_delete_button->set_disabled(selected_indices.size() <= 0 || read_only); } void TileSetScenesCollectionSourceEditor::_update_scenes_list() { @@ -321,20 +328,27 @@ void TileSetScenesCollectionSourceEditor::_update_scenes_list() { } // Icon size update. - int int_size = int(EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size")) * EDSCALE; + int int_size = int(EDITOR_GET("filesystem/file_dialog/thumbnail_size")) * EDSCALE; scene_tiles_list->set_fixed_icon_size(Vector2(int_size, int_size)); } void TileSetScenesCollectionSourceEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { scene_tile_add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); scene_tile_delete_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); _update_scenes_list(); - break; - case NOTIFICATION_INTERNAL_PROCESS: + } break; + + case NOTIFICATION_INTERNAL_PROCESS: { if (tile_set_scenes_collection_source_changed_needs_update) { + read_only = false; + // Add the listener again and check for read-only status. + if (tile_set.is_valid()) { + read_only = EditorNode::get_singleton()->is_resource_read_only(tile_set); + } + // Update everything. _update_source_inspector(); _update_scenes_list(); @@ -342,14 +356,21 @@ void TileSetScenesCollectionSourceEditor::_notification(int p_what) { _update_tile_inspector(); tile_set_scenes_collection_source_changed_needs_update = false; } - break; - case NOTIFICATION_VISIBILITY_CHANGED: + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { // Update things just in case. _update_scenes_list(); _update_action_buttons(); - break; - default: - break; + } break; + + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/localize_settings")) { + EditorPropertyNameProcessor::Style style = EditorPropertyNameProcessor::get_singleton()->get_settings_style(); + scenes_collection_source_inspector->set_property_name_style(style); + tile_inspector->set_property_name_style(style); + } + } break; } } @@ -359,7 +380,12 @@ void TileSetScenesCollectionSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetS ERR_FAIL_COND(p_source_id < 0); ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source); - if (p_tile_set == tile_set && p_tile_set_scenes_collection_source == tile_set_scenes_collection_source && p_source_id == tile_set_source_id) { + bool new_read_only_state = false; + if (p_tile_set.is_valid()) { + new_read_only_state = EditorNode::get_singleton()->is_resource_read_only(p_tile_set); + } + + if (p_tile_set == tile_set && p_tile_set_scenes_collection_source == tile_set_scenes_collection_source && p_source_id == tile_set_source_id && new_read_only_state == read_only) { return; } @@ -373,6 +399,16 @@ void TileSetScenesCollectionSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetS tile_set_scenes_collection_source = p_tile_set_scenes_collection_source; tile_set_source_id = p_source_id; + // Read-only status is false by default + read_only = new_read_only_state; + + if (tile_set.is_valid()) { + scenes_collection_source_inspector->set_read_only(read_only); + tile_inspector->set_read_only(read_only); + + scene_tile_add_button->set_disabled(read_only); + } + // Add the listener again. if (tile_set_scenes_collection_source) { tile_set_scenes_collection_source->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed)); @@ -392,13 +428,13 @@ void TileSetScenesCollectionSourceEditor::_drop_data_fw(const Point2 &p_point, c if (p_from == scene_tiles_list) { // Handle dropping a texture in the list of atlas resources. - int scene_id = -1; Dictionary d = p_data; Vector<String> files = d["files"]; for (int i = 0; i < files.size(); i++) { Ref<PackedScene> resource = ResourceLoader::load(files[i]); if (resource.is_valid()) { - scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id(); + int scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add a Scene Tile")); undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", resource, scene_id); undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id); @@ -447,8 +483,6 @@ void TileSetScenesCollectionSourceEditor::_bind_methods() { ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id"))); ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileSetScenesCollectionSourceEditor::_scene_thumbnail_done); - ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &TileSetScenesCollectionSourceEditor::_can_drop_data_fw); - ClassDB::bind_method(D_METHOD("_drop_data_fw"), &TileSetScenesCollectionSourceEditor::_drop_data_fw); } TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() { @@ -460,7 +494,7 @@ TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() { // Middle panel. ScrollContainer *middle_panel = memnew(ScrollContainer); middle_panel->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); - middle_panel->set_custom_minimum_size(Size2i(200, 0) * EDSCALE); + middle_panel->set_custom_minimum_size(Size2(200, 0) * EDSCALE); split_container_right_side->add_child(middle_panel); VBoxContainer *middle_vbox_container = memnew(VBoxContainer); @@ -476,9 +510,9 @@ TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() { scenes_collection_source_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_collection_source_proxy_object_changed)); scenes_collection_source_inspector = memnew(EditorInspector); - scenes_collection_source_inspector->set_undo_redo(undo_redo); scenes_collection_source_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); scenes_collection_source_inspector->edit(scenes_collection_source_proxy_object); + scenes_collection_source_inspector->set_property_name_style(EditorPropertyNameProcessor::get_singleton()->get_settings_style()); middle_vbox_container->add_child(scenes_collection_source_inspector); // Tile inspector. @@ -492,10 +526,10 @@ TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() { tile_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1)); tile_inspector = memnew(EditorInspector); - tile_inspector->set_undo_redo(undo_redo); tile_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); tile_inspector->edit(tile_proxy_object); tile_inspector->set_use_folding(true); + tile_inspector->set_property_name_style(EditorPropertyNameProcessor::get_singleton()->get_settings_style()); middle_vbox_container->add_child(tile_inspector); // Scenes list. @@ -505,7 +539,7 @@ TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() { scene_tiles_list = memnew(ItemList); scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL); scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL); - scene_tiles_list->set_drag_forwarding(this); + SET_DRAG_FORWARDING_CDU(scene_tiles_list, TileSetScenesCollectionSourceEditor); scene_tiles_list->connect("item_selected", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_tile_inspector).unbind(1)); scene_tiles_list->connect("item_selected", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1)); scene_tiles_list->connect("item_activated", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_list_item_activated)); diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h index 5b48ea4762..2a0e8595c4 100644 --- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h @@ -1,38 +1,40 @@ -/*************************************************************************/ -/* tile_set_scenes_collection_source_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tile_set_scenes_collection_source_editor.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 TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H #define TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H -#include "editor/editor_node.h" +#include "editor/editor_inspector.h" #include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/item_list.h" #include "scene/resources/tile_set.h" class TileSetScenesCollectionSourceEditor : public HBoxContainer { @@ -66,7 +68,7 @@ private: GDCLASS(SceneTileProxyObject, Object); private: - TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor; + TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor = nullptr; TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr; int source_id; @@ -89,27 +91,27 @@ private: }; private: + bool read_only = false; + Ref<TileSet> tile_set; TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr; int tile_set_source_id = -1; - UndoRedo *undo_redo = EditorNode::get_undo_redo(); - bool tile_set_scenes_collection_source_changed_needs_update = false; // Source inspector. - TileSetScenesCollectionProxyObject *scenes_collection_source_proxy_object; - Label *scenes_collection_source_inspector_label; - EditorInspector *scenes_collection_source_inspector; + TileSetScenesCollectionProxyObject *scenes_collection_source_proxy_object = nullptr; + Label *scenes_collection_source_inspector_label = nullptr; + EditorInspector *scenes_collection_source_inspector = nullptr; // Tile inspector. - SceneTileProxyObject *tile_proxy_object; - Label *tile_inspector_label; - EditorInspector *tile_inspector; + SceneTileProxyObject *tile_proxy_object = nullptr; + Label *tile_inspector_label = nullptr; + EditorInspector *tile_inspector = nullptr; - ItemList *scene_tiles_list; - Button *scene_tile_add_button; - Button *scene_tile_delete_button; + ItemList *scene_tiles_list = nullptr; + Button *scene_tile_add_button = nullptr; + Button *scene_tile_delete_button = nullptr; void _tile_set_scenes_collection_source_changed(); void _scenes_collection_source_proxy_object_changed(String p_what); @@ -138,4 +140,4 @@ public: ~TileSetScenesCollectionSourceEditor(); }; -#endif +#endif // TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index f99fcb3675..78522dfa73 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -1,39 +1,42 @@ -/*************************************************************************/ -/* tiles_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tiles_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 "tiles_editor_plugin.h" +#include "tile_set_editor.h" + #include "core/os/mutex.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "scene/2d/tile_map.h" @@ -43,8 +46,6 @@ #include "scene/gui/separator.h" #include "scene/resources/tile_set.h" -#include "tile_set_editor.h" - TilesEditorPlugin *TilesEditorPlugin::singleton = nullptr; void TilesEditorPlugin::_preview_frame_started() { @@ -56,7 +57,7 @@ void TilesEditorPlugin::_pattern_preview_done() { } void TilesEditorPlugin::_thread_func(void *ud) { - TilesEditorPlugin *te = (TilesEditorPlugin *)ud; + TilesEditorPlugin *te = static_cast<TilesEditorPlugin *>(ud); te->_thread(); } @@ -66,12 +67,14 @@ void TilesEditorPlugin::_thread() { pattern_preview_sem.wait(); pattern_preview_mutex.lock(); - if (pattern_preview_queue.size()) { + if (pattern_preview_queue.size() == 0) { + pattern_preview_mutex.unlock(); + } else { QueueItem item = pattern_preview_queue.front()->get(); pattern_preview_queue.pop_front(); pattern_preview_mutex.unlock(); - int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size"); + int thumbnail_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size"); thumbnail_size *= EDSCALE; Vector2 thumbnail_size2 = Vector2(thumbnail_size, thumbnail_size); @@ -90,20 +93,20 @@ void TilesEditorPlugin::_thread() { TypedArray<Vector2i> used_cells = tile_map->get_used_cells(0); - Rect2 encompassing_rect = Rect2(); - encompassing_rect.set_position(tile_map->map_to_world(used_cells[0])); + Rect2 encompassing_rect; + encompassing_rect.set_position(tile_map->map_to_local(used_cells[0])); for (int i = 0; i < used_cells.size(); i++) { Vector2i cell = used_cells[i]; - Vector2 world_pos = tile_map->map_to_world(cell); + Vector2 world_pos = tile_map->map_to_local(cell); encompassing_rect.expand_to(world_pos); // Texture. - Ref<TileSetAtlasSource> atlas_source = tile_set->get_source(tile_map->get_cell_source_id(0, cell)); + Ref<TileSetAtlasSource> atlas_source = item.tile_set->get_source(tile_map->get_cell_source_id(0, cell)); if (atlas_source.is_valid()) { Vector2i coords = tile_map->get_cell_atlas_coords(0, cell); int alternative = tile_map->get_cell_alternative_tile(0, cell); - Vector2 center = world_pos - atlas_source->get_tile_effective_texture_offset(coords, alternative); + Vector2 center = world_pos - atlas_source->get_tile_data(coords, alternative)->get_texture_origin(); encompassing_rect.expand_to(center - atlas_source->get_tile_texture_region(coords).size / 2); encompassing_rect.expand_to(center + atlas_source->get_tile_texture_region(coords).size / 2); } @@ -113,28 +116,23 @@ void TilesEditorPlugin::_thread() { tile_map->set_scale(scale); tile_map->set_position(-(scale * encompassing_rect.get_center()) + thumbnail_size2 / 2); - // Add the viewport at the lasst moment to avoid rendering too early. - EditorNode::get_singleton()->add_child(viewport); + // Add the viewport at the last moment to avoid rendering too early. + EditorNode::get_singleton()->call_deferred("add_child", viewport); - RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<TilesEditorPlugin *>(this), &TilesEditorPlugin::_preview_frame_started), Vector<Variant>(), Object::CONNECT_ONESHOT); + RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<TilesEditorPlugin *>(this), &TilesEditorPlugin::_preview_frame_started), Object::CONNECT_ONE_SHOT); pattern_preview_done.wait(); Ref<Image> image = viewport->get_texture()->get_image(); - Ref<ImageTexture> image_texture; - image_texture.instantiate(); - image_texture->create_from_image(image); // Find the index for the given pattern. TODO: optimize. - Variant args[] = { item.pattern, image_texture }; + Variant args[] = { item.pattern, ImageTexture::create_from_image(image) }; const Variant *args_ptr[] = { &args[0], &args[1] }; Variant r; Callable::CallError error; - item.callback.call(args_ptr, 2, r, error); + item.callback.callp(args_ptr, 2, r, error); - viewport->queue_delete(); - } else { - pattern_preview_mutex.unlock(); + viewport->queue_free(); } } } @@ -158,16 +156,17 @@ void TilesEditorPlugin::_update_editors() { // Update the viewport. CanvasItemEditor::get_singleton()->update_viewport(); + // Make sure the tile set editor is visible if we have one assigned. + tileset_editor_button->set_visible(is_visible && tile_set.is_valid()); + // Update visibility of bottom panel buttons. if (tileset_editor_button->is_pressed() && !tile_set.is_valid()) { if (tile_map) { - editor_node->make_bottom_panel_item_visible(tilemap_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(tilemap_editor); } else { - editor_node->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); } } - tileset_editor_button->set_visible(tile_set.is_valid()); - tilemap_editor_button->set_visible(tile_map); } void TilesEditorPlugin::_notification(int p_what) { @@ -186,24 +185,34 @@ void TilesEditorPlugin::_notification(int p_what) { } void TilesEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { + if (p_visible || is_tile_map_selected()) { // Disable and hide invalid editors. TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); tileset_editor_button->set_visible(tile_set.is_valid()); tilemap_editor_button->set_visible(tile_map); - if (tile_map) { - editor_node->make_bottom_panel_item_visible(tilemap_editor); + if (tile_map && (!is_editing_tile_set || !p_visible)) { + EditorNode::get_singleton()->make_bottom_panel_item_visible(tilemap_editor); } else { - editor_node->make_bottom_panel_item_visible(tileset_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(tileset_editor); } - + is_visible = true; } else { tileset_editor_button->hide(); tilemap_editor_button->hide(); - editor_node->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); + is_visible = false; } } +bool TilesEditorPlugin::is_tile_map_selected() { + TypedArray<Node> selection = get_editor_interface()->get_selection()->get_selected_nodes(); + if (selection.size() == 1 && Object::cast_to<TileMap>(selection[0])) { + return true; + } + + return false; +} + void TilesEditorPlugin::queue_pattern_preview(Ref<TileSet> p_tile_set, Ref<TileMapPattern> p_pattern, Callable p_callback) { ERR_FAIL_COND(!p_tile_set.is_valid()); ERR_FAIL_COND(!p_pattern.is_valid()); @@ -218,15 +227,29 @@ void TilesEditorPlugin::set_sources_lists_current(int p_current) { atlas_sources_lists_current = p_current; } -void TilesEditorPlugin::synchronize_sources_list(Object *p_current) { - ItemList *item_list = Object::cast_to<ItemList>(p_current); +void TilesEditorPlugin::synchronize_sources_list(Object *p_current_list, Object *p_current_sort_button) { + ItemList *item_list = Object::cast_to<ItemList>(p_current_list); + MenuButton *sorting_button = Object::cast_to<MenuButton>(p_current_sort_button); ERR_FAIL_COND(!item_list); + ERR_FAIL_COND(!sorting_button); + + if (sorting_button->is_visible_in_tree()) { + for (int i = 0; i != SOURCE_SORT_MAX; i++) { + sorting_button->get_popup()->set_item_checked(i, (i == (int)source_sort)); + } + } if (item_list->is_visible_in_tree()) { + // Make sure the selection is not overwritten after sorting. + int atlas_sources_lists_current_mem = atlas_sources_lists_current; + item_list->emit_signal(SNAME("sort_request")); + atlas_sources_lists_current = atlas_sources_lists_current_mem; + if (atlas_sources_lists_current < 0 || atlas_sources_lists_current >= item_list->get_item_count()) { item_list->deselect_all(); } else { item_list->set_current(atlas_sources_lists_current); + item_list->ensure_current_is_visible(); item_list->emit_signal(SNAME("item_selected"), atlas_sources_lists_current); } } @@ -246,6 +269,87 @@ void TilesEditorPlugin::synchronize_atlas_view(Object *p_current) { } } +void TilesEditorPlugin::set_sorting_option(int p_option) { + source_sort = p_option; +} + +List<int> TilesEditorPlugin::get_sorted_sources(const Ref<TileSet> p_tile_set) const { + SourceNameComparator::tile_set = p_tile_set; + List<int> source_ids; + + for (int i = 0; i < p_tile_set->get_source_count(); i++) { + source_ids.push_back(p_tile_set->get_source_id(i)); + } + + switch (source_sort) { + case SOURCE_SORT_ID_REVERSE: + // Already sorted. + source_ids.reverse(); + break; + case SOURCE_SORT_NAME: + source_ids.sort_custom<SourceNameComparator>(); + break; + case SOURCE_SORT_NAME_REVERSE: + source_ids.sort_custom<SourceNameComparator>(); + source_ids.reverse(); + break; + default: // SOURCE_SORT_ID + break; + } + + SourceNameComparator::tile_set.unref(); + return source_ids; +} + +Ref<TileSet> TilesEditorPlugin::SourceNameComparator::tile_set; + +bool TilesEditorPlugin::SourceNameComparator::operator()(const int &p_a, const int &p_b) const { + String name_a; + String name_b; + + { + TileSetSource *source = *tile_set->get_source(p_a); + + if (!source->get_name().is_empty()) { + name_a = source->get_name(); + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + Ref<Texture2D> texture = atlas_source->get_texture(); + if (name_a.is_empty() && texture.is_valid()) { + name_a = texture->get_path().get_file(); + } + } + + if (name_a.is_empty()) { + name_a = itos(p_a); + } + } + + { + TileSetSource *source = *tile_set->get_source(p_b); + + if (!source->get_name().is_empty()) { + name_b = source->get_name(); + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + Ref<Texture2D> texture = atlas_source->get_texture(); + if (name_b.is_empty() && texture.is_valid()) { + name_b = texture->get_path().get_file(); + } + } + + if (name_b.is_empty()) { + name_b = itos(p_b); + } + } + + return NaturalNoCaseComparator()(name_a, name_b); +} + void TilesEditorPlugin::edit(Object *p_object) { // Disconnect to changes. TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); @@ -255,27 +359,35 @@ void TilesEditorPlugin::edit(Object *p_object) { // Update edited objects. tile_set = Ref<TileSet>(); + is_editing_tile_set = false; + if (p_object) { if (p_object->is_class("TileMap")) { tile_map_id = p_object->get_instance_id(); tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); tile_set = tile_map->get_tileset(); - editor_node->make_bottom_panel_item_visible(tilemap_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(tilemap_editor); } else if (p_object->is_class("TileSet")) { tile_set = Ref<TileSet>(p_object); if (tile_map) { - if (tile_map->get_tileset() != tile_set || !tile_map->is_inside_tree()) { + if (tile_map->get_tileset() != tile_set || !tile_map->is_inside_tree() || !is_tile_map_selected()) { tile_map = nullptr; tile_map_id = ObjectID(); } } - editor_node->make_bottom_panel_item_visible(tileset_editor); + is_editing_tile_set = true; } } // Update the editors. _update_editors(); + // If the tileset is being edited, the visibility function must be called + // here after _update_editors has been called. + if (is_editing_tile_set) { + EditorNode::get_singleton()->make_bottom_panel_item_visible(tileset_editor); + } + // Add change listener. if (tile_map) { tile_map->connect("changed", callable_mp(this, &TilesEditorPlugin::_tile_map_changed)); @@ -286,14 +398,21 @@ bool TilesEditorPlugin::handles(Object *p_object) const { return p_object->is_class("TileMap") || p_object->is_class("TileSet"); } -TilesEditorPlugin::TilesEditorPlugin(EditorNode *p_node) { +void TilesEditorPlugin::draw_selection_rect(CanvasItem *p_ci, const Rect2 &p_rect, const Color &p_color) { + real_t scale = p_ci->get_global_transform().get_scale().x * 0.5; + p_ci->draw_set_transform(p_rect.position, 0, Vector2(1, 1) / scale); + RS::get_singleton()->canvas_item_add_nine_patch( + p_ci->get_canvas_item(), Rect2(Vector2(), p_rect.size * scale), Rect2(), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("TileSelection"), SNAME("EditorIcons"))->get_rid(), + Vector2(2, 2), Vector2(2, 2), RS::NINE_PATCH_STRETCH, RS::NINE_PATCH_STRETCH, false, p_color); + p_ci->draw_set_transform_matrix(Transform2D()); +} + +TilesEditorPlugin::TilesEditorPlugin() { set_process_internal(true); // Update the singleton. singleton = this; - editor_node = p_node; - // Tileset editor. tileset_editor = memnew(TileSetEditor); tileset_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -312,9 +431,9 @@ TilesEditorPlugin::TilesEditorPlugin(EditorNode *p_node) { pattern_preview_thread.start(_thread_func, this); // Bottom buttons. - tileset_editor_button = p_node->add_bottom_panel_item(TTR("TileSet"), tileset_editor); + tileset_editor_button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("TileSet"), tileset_editor); tileset_editor_button->hide(); - tilemap_editor_button = p_node->add_bottom_panel_item(TTR("TileMap"), tilemap_editor); + tilemap_editor_button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("TileMap"), tilemap_editor); tilemap_editor_button->hide(); // Initialization. diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h index 59eb79480e..50073e59c6 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.h +++ b/editor/plugins/tiles/tiles_editor_plugin.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* tiles_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* tiles_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 TILES_EDITOR_PLUGIN_H #define TILES_EDITOR_PLUGIN_H @@ -43,28 +43,46 @@ class TilesEditorPlugin : public EditorPlugin { static TilesEditorPlugin *singleton; +public: + enum SourceSortOption { + SOURCE_SORT_ID = 0, + SOURCE_SORT_ID_REVERSE, + SOURCE_SORT_NAME, + SOURCE_SORT_NAME_REVERSE, + SOURCE_SORT_MAX + }; + private: - EditorNode *editor_node; + bool is_visible = false; bool tile_map_changed_needs_update = false; ObjectID tile_map_id; Ref<TileSet> tile_set; + bool is_editing_tile_set = false; - Button *tilemap_editor_button; - TileMapEditor *tilemap_editor; + Button *tilemap_editor_button = nullptr; + TileMapEditor *tilemap_editor = nullptr; - Button *tileset_editor_button; - TileSetEditor *tileset_editor; + Button *tileset_editor_button = nullptr; + TileSetEditor *tileset_editor = nullptr; void _update_editors(); // For synchronization. int atlas_sources_lists_current = 0; float atlas_view_zoom = 1.0; - Vector2 atlas_view_scroll = Vector2(); + Vector2 atlas_view_scroll; void _tile_map_changed(); + // Source sorting. + int source_sort = SOURCE_SORT_ID; + + struct SourceNameComparator { + static Ref<TileSet> tile_set; + bool operator()(const int &p_a, const int &p_b) const; + }; + // Patterns preview generation. struct QueueItem { Ref<TileSet> tile_set; @@ -92,21 +110,29 @@ public: virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return tilemap_editor->forward_canvas_gui_input(p_event); } virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { tilemap_editor->forward_canvas_draw_over_viewport(p_overlay); } + bool is_tile_map_selected(); + // Pattern preview API. void queue_pattern_preview(Ref<TileSet> p_tile_set, Ref<TileMapPattern> p_pattern, Callable p_callback); // To synchronize the atlas sources lists. void set_sources_lists_current(int p_current); - void synchronize_sources_list(Object *p_current); + void synchronize_sources_list(Object *p_current_list, Object *p_current_sort_button); void set_atlas_view_transform(float p_zoom, Vector2 p_scroll); void synchronize_atlas_view(Object *p_current); + // Sorting. + void set_sorting_option(int p_option); + List<int> get_sorted_sources(const Ref<TileSet> p_tile_set) const; + virtual void edit(Object *p_object) override; virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - TilesEditorPlugin(EditorNode *p_node); + static void draw_selection_rect(CanvasItem *p_ci, const Rect2 &p_rect, const Color &p_color = Color(1.0, 1.0, 1.0)); + + TilesEditorPlugin(); ~TilesEditorPlugin(); }; diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index b1d5b348c4..c404e12d39 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -1,52 +1,53 @@ -/*************************************************************************/ -/* version_control_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* version_control_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 "version_control_editor_plugin.h" -#include "core/object/script_language.h" +#include "core/config/project_settings.h" #include "core/os/keyboard.h" +#include "core/os/time.h" #include "editor/editor_file_system.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/filesystem_dock.h" +#include "editor/plugins/script_editor_plugin.h" +#include "scene/gui/separator.h" + +#define CHECK_PLUGIN_INITIALIZED() \ + ERR_FAIL_COND_MSG(!EditorVCSInterface::get_singleton(), "No VCS plugin is initialized. Select a Version Control Plugin from Project menu."); VersionControlEditorPlugin *VersionControlEditorPlugin::singleton = nullptr; void VersionControlEditorPlugin::_bind_methods() { - ClassDB::bind_method(D_METHOD("popup_vcs_set_up_dialog"), &VersionControlEditorPlugin::popup_vcs_set_up_dialog); - - // Used to track the status of files in the staging area - BIND_ENUM_CONSTANT(CHANGE_TYPE_NEW); - BIND_ENUM_CONSTANT(CHANGE_TYPE_MODIFIED); - BIND_ENUM_CONSTANT(CHANGE_TYPE_RENAMED); - BIND_ENUM_CONSTANT(CHANGE_TYPE_DELETED); - BIND_ENUM_CONSTANT(CHANGE_TYPE_TYPECHANGE); + // No binds required so far. } void VersionControlEditorPlugin::_create_vcs_metadata_files() { @@ -54,21 +55,23 @@ void VersionControlEditorPlugin::_create_vcs_metadata_files() { EditorVCSInterface::create_vcs_metadata_files(EditorVCSInterface::VCSMetadata(metadata_selection->get_selected()), dir); } -void VersionControlEditorPlugin::_selected_a_vcs(int p_id) { - List<StringName> available_addons = get_available_vcs_names(); - const StringName selected_vcs = set_up_choice->get_item_text(p_id); -} - -void VersionControlEditorPlugin::_populate_available_vcs_names() { - static bool called = false; +void VersionControlEditorPlugin::_notification(int p_what) { + if (p_what == NOTIFICATION_READY) { + String installed_plugin = GLOBAL_DEF("editor/version_control/plugin_name", ""); + bool has_autoload_enable = GLOBAL_DEF("editor/version_control/autoload_on_startup", false); - if (!called) { - List<StringName> available_addons = get_available_vcs_names(); - for (int i = 0; i < available_addons.size(); i++) { - set_up_choice->add_item(available_addons[i]); + if (installed_plugin != "" && has_autoload_enable) { + if (_load_plugin(installed_plugin)) { + _set_credentials(); + } } + } +} - called = true; +void VersionControlEditorPlugin::_populate_available_vcs_names() { + set_up_choice->clear(); + for (int i = 0; i < available_plugins.size(); i++) { + set_up_choice->add_item(available_plugins[i]); } } @@ -81,9 +84,8 @@ void VersionControlEditorPlugin::popup_vcs_metadata_dialog() { } void VersionControlEditorPlugin::popup_vcs_set_up_dialog(const Control *p_gui_base) { - fetch_available_vcs_addon_names(); - List<StringName> available_addons = get_available_vcs_names(); - if (available_addons.size() >= 1) { + fetch_available_vcs_plugin_names(); + if (!available_plugins.is_empty()) { Size2 popup_size = Size2(400, 100); Size2 window_size = p_gui_base->get_viewport_rect().size; popup_size.x = MIN(window_size.x * 0.5, popup_size.x); @@ -93,213 +95,778 @@ void VersionControlEditorPlugin::popup_vcs_set_up_dialog(const Control *p_gui_ba set_up_dialog->popup_centered_clamped(popup_size * EDSCALE); } else { - EditorNode::get_singleton()->show_warning(TTR("No VCS addons are available."), TTR("Error")); + // TODO: Give info to user on how to fix this error. + EditorNode::get_singleton()->show_warning(TTR("No VCS plugins are available in the project. Install a VCS plugin to use VCS integration features."), TTR("Error")); } } void VersionControlEditorPlugin::_initialize_vcs() { - register_editor(); - - ERR_FAIL_COND_MSG(EditorVCSInterface::get_singleton(), EditorVCSInterface::get_singleton()->get_vcs_name() + " is already active"); + ERR_FAIL_COND_MSG(EditorVCSInterface::get_singleton(), EditorVCSInterface::get_singleton()->get_vcs_name() + " is already active."); const int id = set_up_choice->get_selected_id(); - String selected_addon = set_up_choice->get_item_text(id); + String selected_plugin = set_up_choice->get_item_text(id); - String path = ScriptServer::get_global_class_path(selected_addon); - Ref<Script> script = ResourceLoader::load(path); - - ERR_FAIL_COND_MSG(!script.is_valid(), "VCS Addon path is invalid"); + if (_load_plugin(selected_plugin)) { + ProjectSettings::get_singleton()->set("editor/version_control/autoload_on_startup", true); + ProjectSettings::get_singleton()->set("editor/version_control/plugin_name", selected_plugin); + ProjectSettings::get_singleton()->save(); + } +} - EditorVCSInterface *vcs_interface = memnew(EditorVCSInterface); - ScriptInstance *addon_script_instance = script->instance_create(vcs_interface); +void VersionControlEditorPlugin::_set_vcs_ui_state(bool p_enabled) { + set_up_dialog->get_ok_button()->set_disabled(!p_enabled); + set_up_choice->set_disabled(p_enabled); + toggle_vcs_choice->set_pressed_no_signal(p_enabled); +} - ERR_FAIL_COND_MSG(!addon_script_instance, "Failed to create addon script instance."); +void VersionControlEditorPlugin::_set_credentials() { + CHECK_PLUGIN_INITIALIZED(); + + String username = set_up_username->get_text(); + String password = set_up_password->get_text(); + String ssh_public_key = set_up_ssh_public_key_path->get_text(); + String ssh_private_key = set_up_ssh_private_key_path->get_text(); + String ssh_passphrase = set_up_ssh_passphrase->get_text(); + + EditorVCSInterface::get_singleton()->set_credentials( + username, + password, + ssh_public_key, + ssh_private_key, + ssh_passphrase); + + EditorSettings::get_singleton()->set_setting("version_control/username", username); + EditorSettings::get_singleton()->set_setting("version_control/ssh_public_key_path", ssh_public_key); + EditorSettings::get_singleton()->set_setting("version_control/ssh_private_key_path", ssh_private_key); +} - // The addon is attached as a script to the VCS interface as a proxy end-point - vcs_interface->set_script_and_instance(script, addon_script_instance); +bool VersionControlEditorPlugin::_load_plugin(String p_name) { + Object *extension_instance = ClassDB::instantiate(p_name); + ERR_FAIL_NULL_V_MSG(extension_instance, false, "Received a nullptr VCS extension instance during construction."); - EditorVCSInterface::set_singleton(vcs_interface); - EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); + EditorVCSInterface *vcs_plugin = Object::cast_to<EditorVCSInterface>(extension_instance); + ERR_FAIL_NULL_V_MSG(vcs_plugin, false, vformat("Could not cast VCS extension instance to %s.", EditorVCSInterface::get_class_static())); String res_dir = OS::get_singleton()->get_resource_dir(); - ERR_FAIL_COND_MSG(!EditorVCSInterface::get_singleton()->initialize(res_dir), "VCS was not initialized"); + ERR_FAIL_COND_V_MSG(!vcs_plugin->initialize(res_dir), false, "Could not initialize " + p_name); + + EditorVCSInterface::set_singleton(vcs_plugin); + + register_editor(); + EditorFileSystem::get_singleton()->connect(SNAME("filesystem_changed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); _refresh_stage_area(); + _refresh_commit_list(); + _refresh_branch_list(); + _refresh_remote_list(); + + return true; } -void VersionControlEditorPlugin::_send_commit_msg() { - if (EditorVCSInterface::get_singleton()) { - if (staged_files_count == 0) { - commit_status->set_text(TTR("No files added to stage")); - return; +void VersionControlEditorPlugin::_update_set_up_warning(String p_new_text) { + bool empty_settings = set_up_username->get_text().strip_edges().is_empty() && + set_up_password->get_text().is_empty() && + set_up_ssh_public_key_path->get_text().strip_edges().is_empty() && + set_up_ssh_private_key_path->get_text().strip_edges().is_empty() && + set_up_ssh_passphrase->get_text().is_empty(); + + if (empty_settings) { + set_up_warning_text->add_theme_color_override(SNAME("font_color"), EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + set_up_warning_text->set_text(TTR("Remote settings are empty. VCS features that use the network may not work.")); + } else { + set_up_warning_text->set_text(""); + } +} + +void VersionControlEditorPlugin::_refresh_branch_list() { + CHECK_PLUGIN_INITIALIZED(); + + List<String> branch_list = EditorVCSInterface::get_singleton()->get_branch_list(); + branch_select->clear(); + + branch_select->set_disabled(branch_list.is_empty()); + + String current_branch = EditorVCSInterface::get_singleton()->get_current_branch_name(); + + for (int i = 0; i < branch_list.size(); i++) { + branch_select->add_icon_item(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("VcsBranches"), SNAME("EditorIcons")), branch_list[i], i); + + if (branch_list[i] == current_branch) { + branch_select->select(i); } + } +} - EditorVCSInterface::get_singleton()->commit(commit_message->get_text()); +String VersionControlEditorPlugin::_get_date_string_from(int64_t p_unix_timestamp, int64_t p_offset_minutes) const { + return vformat( + "%s %s", + Time::get_singleton()->get_datetime_string_from_unix_time(p_unix_timestamp + p_offset_minutes * 60, true), + Time::get_singleton()->get_offset_string_from_offset_minutes(p_offset_minutes)); +} - commit_message->set_text(""); - version_control_dock_button->set_pressed(false); - } else { - WARN_PRINT("No VCS addon is initialized. Select a Version Control Addon from Project menu"); +void VersionControlEditorPlugin::_set_commit_list_size(int p_index) { + _refresh_commit_list(); +} + +void VersionControlEditorPlugin::_refresh_commit_list() { + CHECK_PLUGIN_INITIALIZED(); + + commit_list->get_root()->clear_children(); + + List<EditorVCSInterface::Commit> commit_info_list = EditorVCSInterface::get_singleton()->get_previous_commits(commit_list_size_button->get_selected_metadata()); + + for (List<EditorVCSInterface::Commit>::Element *e = commit_info_list.front(); e; e = e->next()) { + EditorVCSInterface::Commit commit = e->get(); + TreeItem *item = commit_list->create_item(); + + // Only display the first line of a commit message + int line_ending = commit.msg.find_char('\n'); + String commit_display_msg = commit.msg.substr(0, line_ending); + String commit_date_string = _get_date_string_from(commit.unix_timestamp, commit.offset_minutes); + + Dictionary meta_data; + meta_data[SNAME("commit_id")] = commit.id; + meta_data[SNAME("commit_title")] = commit_display_msg; + meta_data[SNAME("commit_subtitle")] = commit.msg.substr(line_ending).strip_edges(); + meta_data[SNAME("commit_unix_timestamp")] = commit.unix_timestamp; + meta_data[SNAME("commit_author")] = commit.author; + meta_data[SNAME("commit_date_string")] = commit_date_string; + + item->set_text(0, commit_display_msg); + item->set_text(1, commit.author.strip_edges()); + item->set_metadata(0, meta_data); } +} + +void VersionControlEditorPlugin::_refresh_remote_list() { + CHECK_PLUGIN_INITIALIZED(); + + List<String> remotes = EditorVCSInterface::get_singleton()->get_remotes(); + + String current_remote = remote_select->get_selected_metadata(); + remote_select->clear(); + + remote_select->set_disabled(remotes.is_empty()); + + for (int i = 0; i < remotes.size(); i++) { + remote_select->add_icon_item(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons")), remotes[i], i); + remote_select->set_item_metadata(i, remotes[i]); + + if (remotes[i] == current_remote) { + remote_select->select(i); + } + } +} + +void VersionControlEditorPlugin::_commit() { + CHECK_PLUGIN_INITIALIZED(); + + String msg = commit_message->get_text().strip_edges(); + + ERR_FAIL_COND_MSG(msg.is_empty(), "No commit message was provided."); + + EditorVCSInterface::get_singleton()->commit(msg); + + version_control_dock_button->set_pressed(false); + + commit_message->release_focus(); + commit_button->release_focus(); + commit_message->set_text(""); - _update_commit_status(); _refresh_stage_area(); - _clear_file_diff(); + _refresh_commit_list(); + _refresh_branch_list(); + _clear_diff(); +} + +void VersionControlEditorPlugin::_branch_item_selected(int p_index) { + CHECK_PLUGIN_INITIALIZED(); + + String branch_name = branch_select->get_item_text(p_index); + EditorVCSInterface::get_singleton()->checkout_branch(branch_name); + + EditorFileSystem::get_singleton()->scan_changes(); + ScriptEditor::get_singleton()->reload_scripts(); + + _refresh_branch_list(); + _refresh_commit_list(); + _refresh_stage_area(); + _clear_diff(); + + _update_opened_tabs(); +} + +void VersionControlEditorPlugin::_remote_selected(int p_index) { + _refresh_remote_list(); +} + +void VersionControlEditorPlugin::_ssh_public_key_selected(String p_path) { + set_up_ssh_public_key_path->set_text(p_path); +} + +void VersionControlEditorPlugin::_ssh_private_key_selected(String p_path) { + set_up_ssh_private_key_path->set_text(p_path); +} + +void VersionControlEditorPlugin::_popup_file_dialog(Variant p_file_dialog_variant) { + FileDialog *file_dialog = Object::cast_to<FileDialog>(p_file_dialog_variant); + ERR_FAIL_NULL(file_dialog); + + file_dialog->popup_centered_ratio(); +} + +void VersionControlEditorPlugin::_create_branch() { + CHECK_PLUGIN_INITIALIZED(); + + String new_branch_name = branch_create_name_input->get_text().strip_edges(); + + EditorVCSInterface::get_singleton()->create_branch(new_branch_name); + EditorVCSInterface::get_singleton()->checkout_branch(new_branch_name); + + branch_create_name_input->clear(); + _refresh_branch_list(); +} + +void VersionControlEditorPlugin::_create_remote() { + CHECK_PLUGIN_INITIALIZED(); + + String new_remote_name = remote_create_name_input->get_text().strip_edges(); + String new_remote_url = remote_create_url_input->get_text().strip_edges(); + + EditorVCSInterface::get_singleton()->create_remote(new_remote_name, new_remote_url); + + remote_create_name_input->clear(); + remote_create_url_input->clear(); + _refresh_remote_list(); +} + +void VersionControlEditorPlugin::_update_branch_create_button(String p_new_text) { + branch_create_ok->set_disabled(p_new_text.strip_edges().is_empty()); +} + +void VersionControlEditorPlugin::_update_remote_create_button(String p_new_text) { + remote_create_ok->set_disabled(p_new_text.strip_edges().is_empty()); +} + +int VersionControlEditorPlugin::_get_item_count(Tree *p_tree) { + if (!p_tree->get_root()) { + return 0; + } + return p_tree->get_root()->get_children().size(); } void VersionControlEditorPlugin::_refresh_stage_area() { - if (EditorVCSInterface::get_singleton()) { - staged_files_count = 0; - clear_stage_area(); - - Dictionary modified_file_paths = EditorVCSInterface::get_singleton()->get_modified_files_data(); - String file_path; - for (int i = 0; i < modified_file_paths.size(); i++) { - file_path = modified_file_paths.get_key_at_index(i); - TreeItem *found = stage_files->search_item_text(file_path, nullptr, true); - if (!found) { - ChangeType change_index = (ChangeType)(int)modified_file_paths.get_value_at_index(i); - String change_text = file_path + " (" + change_type_to_strings[change_index] + ")"; - Color &change_color = change_type_to_color[change_index]; - TreeItem *new_item = stage_files->create_item(stage_files->get_root()); - new_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); - new_item->set_text(0, change_text); - new_item->set_metadata(0, file_path); - new_item->set_custom_color(0, change_color); - new_item->set_checked(0, true); - new_item->set_editable(0, true); - } else { - if (found->get_metadata(0) == diff_file_name->get_text()) { - _refresh_file_diff(); - } - } - commit_status->set_text(TTR("New changes detected")); + CHECK_PLUGIN_INITIALIZED(); + + staged_files->get_root()->clear_children(); + unstaged_files->get_root()->clear_children(); + + List<EditorVCSInterface::StatusFile> status_files = EditorVCSInterface::get_singleton()->get_modified_files_data(); + for (List<EditorVCSInterface::StatusFile>::Element *E = status_files.front(); E; E = E->next()) { + EditorVCSInterface::StatusFile sf = E->get(); + if (sf.area == EditorVCSInterface::TREE_AREA_STAGED) { + _add_new_item(staged_files, sf.file_path, sf.change_type); + } else if (sf.area == EditorVCSInterface::TREE_AREA_UNSTAGED) { + _add_new_item(unstaged_files, sf.file_path, sf.change_type); } + } + + staged_files->queue_redraw(); + unstaged_files->queue_redraw(); + + int total_changes = status_files.size(); + String commit_tab_title = TTR("Commit") + (total_changes > 0 ? " (" + itos(total_changes) + ")" : ""); + version_commit_dock->set_name(commit_tab_title); +} + +void VersionControlEditorPlugin::_discard_file(String p_file_path, EditorVCSInterface::ChangeType p_change) { + CHECK_PLUGIN_INITIALIZED(); + + if (p_change == EditorVCSInterface::CHANGE_TYPE_NEW) { + Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_RESOURCES); + dir->remove(p_file_path); } else { - WARN_PRINT("No VCS addon is initialized. Select a Version Control Addon from Project menu."); + CHECK_PLUGIN_INITIALIZED(); + EditorVCSInterface::get_singleton()->discard_file(p_file_path); } + // FIXIT: The project.godot file shows weird behavior + EditorFileSystem::get_singleton()->update_file(p_file_path); } -void VersionControlEditorPlugin::_stage_selected() { - if (!EditorVCSInterface::get_singleton()) { - WARN_PRINT("No VCS addon is initialized. Select a Version Control Addon from Project menu"); - return; +void VersionControlEditorPlugin::_confirm_discard_all() { + discard_all_confirm->popup_centered(); +} + +void VersionControlEditorPlugin::_discard_all() { + TreeItem *file_entry = unstaged_files->get_root()->get_first_child(); + while (file_entry) { + String file_path = file_entry->get_meta(SNAME("file_path")); + EditorVCSInterface::ChangeType change = (EditorVCSInterface::ChangeType)(int)file_entry->get_meta(SNAME("change_type")); + _discard_file(file_path, change); + + file_entry = file_entry->get_next(); } + _refresh_stage_area(); +} - staged_files_count = 0; - TreeItem *root = stage_files->get_root(); - if (root) { - TreeItem *file_entry = root->get_first_child(); - while (file_entry) { - if (file_entry->is_checked(0)) { - EditorVCSInterface::get_singleton()->stage_file(file_entry->get_metadata(0)); - file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor"))); - staged_files_count++; - } else { - EditorVCSInterface::get_singleton()->unstage_file(file_entry->get_metadata(0)); - file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); - } +void VersionControlEditorPlugin::_add_new_item(Tree *p_tree, String p_file_path, EditorVCSInterface::ChangeType p_change) { + String change_text = p_file_path + " (" + change_type_to_strings[p_change] + ")"; - file_entry = file_entry->get_next(); - } + TreeItem *new_item = p_tree->create_item(); + new_item->set_text(0, change_text); + new_item->set_icon(0, change_type_to_icon[p_change]); + new_item->set_meta(SNAME("file_path"), p_file_path); + new_item->set_meta(SNAME("change_type"), p_change); + new_item->set_custom_color(0, change_type_to_color[p_change]); + + new_item->add_button(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("File"), SNAME("EditorIcons")), BUTTON_TYPE_OPEN, false, TTR("Open in editor")); + if (p_tree == unstaged_files) { + new_item->add_button(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Close"), SNAME("EditorIcons")), BUTTON_TYPE_DISCARD, false, TTR("Discard changes")); } +} - _update_stage_status(); +void VersionControlEditorPlugin::_fetch() { + CHECK_PLUGIN_INITIALIZED(); + + EditorVCSInterface::get_singleton()->fetch(remote_select->get_selected_metadata()); + _refresh_branch_list(); } -void VersionControlEditorPlugin::_stage_all() { - if (!EditorVCSInterface::get_singleton()) { - WARN_PRINT("No VCS addon is initialized. Select a Version Control Addon from Project menu"); - return; - } +void VersionControlEditorPlugin::_pull() { + CHECK_PLUGIN_INITIALIZED(); + + EditorVCSInterface::get_singleton()->pull(remote_select->get_selected_metadata()); + _refresh_stage_area(); + _refresh_branch_list(); + _refresh_commit_list(); + _clear_diff(); + _update_opened_tabs(); +} + +void VersionControlEditorPlugin::_push() { + CHECK_PLUGIN_INITIALIZED(); + + EditorVCSInterface::get_singleton()->push(remote_select->get_selected_metadata(), false); +} - staged_files_count = 0; - TreeItem *root = stage_files->get_root(); - if (root) { - TreeItem *file_entry = root->get_first_child(); - while (file_entry) { - EditorVCSInterface::get_singleton()->stage_file(file_entry->get_metadata(0)); - file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor"))); - file_entry->set_checked(0, true); - staged_files_count++; +void VersionControlEditorPlugin::_force_push() { + CHECK_PLUGIN_INITIALIZED(); - file_entry = file_entry->get_next(); + EditorVCSInterface::get_singleton()->push(remote_select->get_selected_metadata(), true); +} + +void VersionControlEditorPlugin::_update_opened_tabs() { + Vector<EditorData::EditedScene> open_scenes = EditorNode::get_singleton()->get_editor_data().get_edited_scenes(); + for (int i = 0; i < open_scenes.size(); i++) { + if (open_scenes[i].root == NULL) { + continue; } + EditorNode::get_singleton()->reload_scene(open_scenes[i].path); } +} + +void VersionControlEditorPlugin::_move_all(Object *p_tree) { + Tree *tree = Object::cast_to<Tree>(p_tree); - _update_stage_status(); + TreeItem *file_entry = tree->get_root()->get_first_child(); + while (file_entry) { + _move_item(tree, file_entry); + + file_entry = file_entry->get_next(); + } + _refresh_stage_area(); } -void VersionControlEditorPlugin::_view_file_diff() { +void VersionControlEditorPlugin::_load_diff(Object *p_tree) { + CHECK_PLUGIN_INITIALIZED(); + version_control_dock_button->set_pressed(true); - String file_path = stage_files->get_selected()->get_metadata(0); + Tree *tree = Object::cast_to<Tree>(p_tree); + if (tree == staged_files) { + show_commit_diff_header = false; + String file_path = tree->get_selected()->get_meta(SNAME("file_path")); + diff_title->set_text(TTR("Staged Changes")); + diff_content = EditorVCSInterface::get_singleton()->get_diff(file_path, EditorVCSInterface::TREE_AREA_STAGED); + } else if (tree == unstaged_files) { + show_commit_diff_header = false; + String file_path = tree->get_selected()->get_meta(SNAME("file_path")); + diff_title->set_text(TTR("Unstaged Changes")); + diff_content = EditorVCSInterface::get_singleton()->get_diff(file_path, EditorVCSInterface::TREE_AREA_UNSTAGED); + } else if (tree == commit_list) { + show_commit_diff_header = true; + Dictionary meta_data = tree->get_selected()->get_metadata(0); + String commit_id = meta_data[SNAME("commit_id")]; + String commit_title = meta_data[SNAME("commit_title")]; + diff_title->set_text(commit_title); + diff_content = EditorVCSInterface::get_singleton()->get_diff(commit_id, EditorVCSInterface::TREE_AREA_COMMIT); + } + _display_diff(0); +} - _display_file_diff(file_path); +void VersionControlEditorPlugin::_clear_diff() { + diff->clear(); + diff_content.clear(); + diff_title->set_text(""); } -void VersionControlEditorPlugin::_display_file_diff(String p_file_path) { - Array diff_content = EditorVCSInterface::get_singleton()->get_file_diff(p_file_path); +void VersionControlEditorPlugin::_item_activated(Object *p_tree) { + Tree *tree = Object::cast_to<Tree>(p_tree); - diff_file_name->set_text(p_file_path); + _move_item(tree, tree->get_selected()); + _refresh_stage_area(); +} + +void VersionControlEditorPlugin::_move_item(Tree *p_tree, TreeItem *p_item) { + CHECK_PLUGIN_INITIALIZED(); + + if (p_tree == staged_files) { + EditorVCSInterface::get_singleton()->unstage_file(p_item->get_meta(SNAME("file_path"))); + } else { + EditorVCSInterface::get_singleton()->stage_file(p_item->get_meta(SNAME("file_path"))); + } +} + +void VersionControlEditorPlugin::_cell_button_pressed(Object *p_item, int p_column, int p_id, int p_mouse_button_index) { + TreeItem *item = Object::cast_to<TreeItem>(p_item); + String file_path = item->get_meta(SNAME("file_path")); + EditorVCSInterface::ChangeType change = (EditorVCSInterface::ChangeType)(int)item->get_meta(SNAME("change_type")); + + if (p_id == BUTTON_TYPE_OPEN && change != EditorVCSInterface::CHANGE_TYPE_DELETED) { + Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (!dir->file_exists(file_path)) { + return; + } + + file_path = "res://" + file_path; + if (ResourceLoader::get_resource_type(file_path) == "PackedScene") { + EditorNode::get_singleton()->open_request(file_path); + } else if (file_path.ends_with(".gd")) { + EditorNode::get_singleton()->load_resource(file_path); + ScriptEditor::get_singleton()->reload_scripts(); + } else { + FileSystemDock::get_singleton()->navigate_to_path(file_path); + } + + } else if (p_id == BUTTON_TYPE_DISCARD) { + _discard_file(file_path, change); + _refresh_stage_area(); + } +} + +void VersionControlEditorPlugin::_display_diff(int p_idx) { + DiffViewType diff_view = (DiffViewType)diff_view_type_select->get_selected(); diff->clear(); - diff->push_font(EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("source"), SNAME("EditorFonts"))); + + if (show_commit_diff_header) { + Dictionary meta_data = commit_list->get_selected()->get_metadata(0); + String commit_id = meta_data[SNAME("commit_id")]; + String commit_subtitle = meta_data[SNAME("commit_subtitle")]; + String commit_date = meta_data[SNAME("commit_date")]; + String commit_author = meta_data[SNAME("commit_author")]; + String commit_date_string = meta_data[SNAME("commit_date_string")]; + + diff->push_font(EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("doc_bold"), SNAME("EditorFonts"))); + diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("accent_color"), SNAME("Editor"))); + diff->add_text(TTR("Commit:") + " " + commit_id); + diff->add_newline(); + diff->add_text(TTR("Author:") + " " + commit_author); + diff->add_newline(); + diff->add_text(TTR("Date:") + " " + commit_date_string); + diff->add_newline(); + if (!commit_subtitle.is_empty()) { + diff->add_text(TTR("Subtitle:") + " " + commit_subtitle); + diff->add_newline(); + } + diff->add_newline(); + diff->pop(); + diff->pop(); + } + for (int i = 0; i < diff_content.size(); i++) { - Dictionary line_result = diff_content[i]; + EditorVCSInterface::DiffFile diff_file = diff_content[i]; + + diff->push_font(EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("doc_bold"), SNAME("EditorFonts"))); + diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("accent_color"), SNAME("Editor"))); + diff->add_text(TTR("File:") + " " + diff_file.new_file); + diff->pop(); + diff->pop(); + + diff->push_font(EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); + for (int j = 0; j < diff_file.diff_hunks.size(); j++) { + EditorVCSInterface::DiffHunk hunk = diff_file.diff_hunks[j]; + + String old_start = String::num_int64(hunk.old_start); + String new_start = String::num_int64(hunk.new_start); + String old_lines = String::num_int64(hunk.old_lines); + String new_lines = String::num_int64(hunk.new_lines); + + diff->add_newline(); + diff->append_text("[center]@@ " + old_start + "," + old_lines + " " + new_start + "," + new_lines + " @@[/center]"); + diff->add_newline(); + + switch (diff_view) { + case DIFF_VIEW_TYPE_SPLIT: + _display_diff_split_view(hunk.diff_lines); + break; + case DIFF_VIEW_TYPE_UNIFIED: + _display_diff_unified_view(hunk.diff_lines); + break; + } + diff->add_newline(); + } + diff->pop(); + + diff->add_newline(); + } +} + +void VersionControlEditorPlugin::_display_diff_split_view(List<EditorVCSInterface::DiffLine> &p_diff_content) { + List<EditorVCSInterface::DiffLine> parsed_diff; + + for (int i = 0; i < p_diff_content.size(); i++) { + EditorVCSInterface::DiffLine diff_line = p_diff_content[i]; + String line = diff_line.content.strip_edges(false, true); + + if (diff_line.new_line_no >= 0 && diff_line.old_line_no >= 0) { + diff_line.new_text = line; + diff_line.old_text = line; + parsed_diff.push_back(diff_line); + } else if (diff_line.new_line_no == -1) { + diff_line.new_text = ""; + diff_line.old_text = line; + parsed_diff.push_back(diff_line); + } else if (diff_line.old_line_no == -1) { + int j = parsed_diff.size() - 1; + while (j >= 0 && parsed_diff[j].new_line_no == -1) { + j--; + } + + if (j == parsed_diff.size() - 1) { + // no lines are modified + diff_line.new_text = line; + diff_line.old_text = ""; + parsed_diff.push_back(diff_line); + } else { + // lines are modified + EditorVCSInterface::DiffLine modified_line = parsed_diff[j + 1]; + modified_line.new_text = line; + modified_line.new_line_no = diff_line.new_line_no; + parsed_diff[j + 1] = modified_line; + } + } + } + + diff->push_table(6); + /* + [cell]Old Line No[/cell] + [cell]prefix[/cell] + [cell]Old Code[/cell] + + [cell]New Line No[/cell] + [cell]prefix[/cell] + [cell]New Line[/cell] + */ + + diff->set_table_column_expand(2, true); + diff->set_table_column_expand(5, true); + + for (int i = 0; i < parsed_diff.size(); i++) { + EditorVCSInterface::DiffLine diff_line = parsed_diff[i]; + + bool has_change = diff_line.status != " "; + static const Color red = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")); + static const Color green = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor")); + static const Color white = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Label")) * Color(1, 1, 1, 0.6); + + if (diff_line.old_line_no >= 0) { + diff->push_cell(); + diff->push_color(has_change ? red : white); + diff->add_text(String::num_int64(diff_line.old_line_no)); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(has_change ? red : white); + diff->add_text(has_change ? "-|" : " |"); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(has_change ? red : white); + diff->add_text(diff_line.old_text); + diff->pop(); + diff->pop(); - if (line_result["status"] == "+") { - diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor"))); - } else if (line_result["status"] == "-") { - diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); } else { - diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Label"))); + diff->push_cell(); + diff->pop(); + + diff->push_cell(); + diff->pop(); + + diff->push_cell(); + diff->pop(); } - diff->add_text((String)line_result["content"]); + if (diff_line.new_line_no >= 0) { + diff->push_cell(); + diff->push_color(has_change ? green : white); + diff->add_text(String::num_int64(diff_line.new_line_no)); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(has_change ? green : white); + diff->add_text(has_change ? "+|" : " |"); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(has_change ? green : white); + diff->add_text(diff_line.new_text); + diff->pop(); + diff->pop(); + } else { + diff->push_cell(); + diff->pop(); - diff->pop(); + diff->push_cell(); + diff->pop(); + + diff->push_cell(); + diff->pop(); + } } diff->pop(); } -void VersionControlEditorPlugin::_refresh_file_diff() { - String open_file = diff_file_name->get_text(); - if (!open_file.is_empty()) { - _display_file_diff(diff_file_name->get_text()); +void VersionControlEditorPlugin::_display_diff_unified_view(List<EditorVCSInterface::DiffLine> &p_diff_content) { + diff->push_table(4); + diff->set_table_column_expand(3, true); + + /* + [cell]Old Line No[/cell] + [cell]New Line No[/cell] + [cell]status[/cell] + [cell]code[/cell] + */ + for (int i = 0; i < p_diff_content.size(); i++) { + EditorVCSInterface::DiffLine diff_line = p_diff_content[i]; + String line = diff_line.content.strip_edges(false, true); + + Color color; + if (diff_line.status == "+") { + color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor")); + } else if (diff_line.status == "-") { + color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")); + } else { + color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Label")); + color *= Color(1, 1, 1, 0.6); + } + + diff->push_cell(); + diff->push_color(color); + diff->push_indent(1); + diff->add_text(diff_line.old_line_no >= 0 ? String::num_int64(diff_line.old_line_no) : ""); + diff->pop(); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(color); + diff->push_indent(1); + diff->add_text(diff_line.new_line_no >= 0 ? String::num_int64(diff_line.new_line_no) : ""); + diff->pop(); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(color); + diff->add_text(diff_line.status != "" ? diff_line.status + "|" : " |"); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(color); + diff->add_text(line); + diff->pop(); + diff->pop(); } + + diff->pop(); } -void VersionControlEditorPlugin::_clear_file_diff() { - diff->clear(); - diff_file_name->set_text(""); - version_control_dock_button->set_pressed(false); +void VersionControlEditorPlugin::_update_commit_button() { + commit_button->set_disabled(commit_message->get_text().strip_edges().is_empty()); } -void VersionControlEditorPlugin::_update_stage_status() { - String status; - if (staged_files_count == 1) { - status = TTR("Stage contains 1 file"); - } else { - status = vformat(TTR("Stage contains %d files"), staged_files_count); +void VersionControlEditorPlugin::_remove_branch() { + CHECK_PLUGIN_INITIALIZED(); + + EditorVCSInterface::get_singleton()->remove_branch(branch_to_remove); + branch_to_remove.clear(); + + _refresh_branch_list(); +} + +void VersionControlEditorPlugin::_remove_remote() { + CHECK_PLUGIN_INITIALIZED(); + + EditorVCSInterface::get_singleton()->remove_remote(remote_to_remove); + remote_to_remove.clear(); + + _refresh_remote_list(); +} + +void VersionControlEditorPlugin::_extra_option_selected(int p_index) { + CHECK_PLUGIN_INITIALIZED(); + + switch ((ExtraOption)p_index) { + case EXTRA_OPTION_FORCE_PUSH: + _force_push(); + break; + case EXTRA_OPTION_CREATE_BRANCH: + branch_create_confirm->popup_centered(); + break; + case EXTRA_OPTION_CREATE_REMOTE: + remote_create_confirm->popup_centered(); + break; } - commit_status->set_text(status); } -void VersionControlEditorPlugin::_update_commit_status() { - String status; - if (staged_files_count == 1) { - status = TTR("Committed 1 file"); - } else { - status = vformat(TTR("Committed %d files"), staged_files_count); +void VersionControlEditorPlugin::_popup_branch_remove_confirm(int p_index) { + branch_to_remove = extra_options_remove_branch_list->get_item_text(p_index); + + branch_remove_confirm->set_text(vformat(TTR("Do you want to remove the %s branch?"), branch_to_remove)); + branch_remove_confirm->popup_centered(); +} + +void VersionControlEditorPlugin::_popup_remote_remove_confirm(int p_index) { + remote_to_remove = extra_options_remove_remote_list->get_item_text(p_index); + + remote_remove_confirm->set_text(vformat(TTR("Do you want to remove the %s remote?"), branch_to_remove)); + remote_remove_confirm->popup_centered(); +} + +void VersionControlEditorPlugin::_update_extra_options() { + extra_options_remove_branch_list->clear(); + for (int i = 0; i < branch_select->get_item_count(); i++) { + extra_options_remove_branch_list->add_icon_item(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("VcsBranches"), SNAME("EditorIcons")), branch_select->get_item_text(branch_select->get_item_id(i))); + } + extra_options_remove_branch_list->update_canvas_items(); + + extra_options_remove_remote_list->clear(); + for (int i = 0; i < remote_select->get_item_count(); i++) { + extra_options_remove_remote_list->add_icon_item(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons")), remote_select->get_item_text(remote_select->get_item_id(i))); } - commit_status->set_text(status); - staged_files_count = 0; + extra_options_remove_remote_list->update_canvas_items(); } -void VersionControlEditorPlugin::_update_commit_button() { - commit_button->set_disabled(commit_message->get_text().strip_edges().is_empty()); +bool VersionControlEditorPlugin::_is_staging_area_empty() { + return staged_files->get_root()->get_child_count() == 0; } void VersionControlEditorPlugin::_commit_message_gui_input(const Ref<InputEvent> &p_event) { @@ -314,266 +881,637 @@ void VersionControlEditorPlugin::_commit_message_gui_input(const Ref<InputEvent> if (k.is_valid() && k->is_pressed()) { if (ED_IS_SHORTCUT("version_control/commit", p_event)) { - if (staged_files_count == 0) { + if (_is_staging_area_empty()) { // Stage all files only when no files were previously staged. - _stage_all(); + _move_all(unstaged_files); } - _send_commit_msg(); + + _commit(); + commit_message->accept_event(); - return; } } } -void VersionControlEditorPlugin::register_editor() { - if (!EditorVCSInterface::get_singleton()) { - EditorNode::get_singleton()->add_control_to_dock(EditorNode::DOCK_SLOT_RIGHT_UL, version_commit_dock); - TabContainer *dock_vbc = (TabContainer *)version_commit_dock->get_parent_control(); - dock_vbc->set_tab_title(version_commit_dock->get_index(), TTR("Commit")); - - Button *vc = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Version Control"), version_control_dock); - set_version_control_tool_button(vc); +void VersionControlEditorPlugin::_toggle_vcs_integration(bool p_toggled) { + if (p_toggled) { + _initialize_vcs(); + } else { + shut_down(); } } -void VersionControlEditorPlugin::fetch_available_vcs_addon_names() { - List<StringName> global_classes; - ScriptServer::get_global_class_list(&global_classes); +void VersionControlEditorPlugin::fetch_available_vcs_plugin_names() { + available_plugins.clear(); + ClassDB::get_direct_inheriters_from_class(EditorVCSInterface::get_class_static(), &available_plugins); +} - for (int i = 0; i != global_classes.size(); i++) { - String path = ScriptServer::get_global_class_path(global_classes[i]); - Ref<Script> script = ResourceLoader::load(path); - ERR_FAIL_COND(script.is_null()); +void VersionControlEditorPlugin::register_editor() { + EditorNode::get_singleton()->add_control_to_dock(EditorNode::DOCK_SLOT_RIGHT_UL, version_commit_dock); - if (script->get_instance_base_type() == "EditorVCSInterface") { - available_addons.push_back(global_classes[i]); - } - } -} + version_control_dock_button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Version Control"), version_control_dock); -void VersionControlEditorPlugin::clear_stage_area() { - stage_files->get_root()->clear_children(); + _set_vcs_ui_state(true); } void VersionControlEditorPlugin::shut_down() { - if (EditorVCSInterface::get_singleton()) { - if (EditorFileSystem::get_singleton()->is_connected("filesystem_changed", callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area))) { - EditorFileSystem::get_singleton()->disconnect("filesystem_changed", callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); - } - EditorVCSInterface::get_singleton()->shut_down(); - memdelete(EditorVCSInterface::get_singleton()); - EditorVCSInterface::set_singleton(nullptr); + if (!EditorVCSInterface::get_singleton()) { + return; + } - EditorNode::get_singleton()->remove_control_from_dock(version_commit_dock); - EditorNode::get_singleton()->remove_bottom_panel_item(version_control_dock); + if (EditorFileSystem::get_singleton()->is_connected(SNAME("filesystem_changed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area))) { + EditorFileSystem::get_singleton()->disconnect(SNAME("filesystem_changed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); } -} -bool VersionControlEditorPlugin::is_vcs_initialized() const { - return EditorVCSInterface::get_singleton() ? EditorVCSInterface::get_singleton()->is_vcs_initialized() : false; -} + EditorVCSInterface::get_singleton()->shut_down(); + memdelete(EditorVCSInterface::get_singleton()); + EditorVCSInterface::set_singleton(nullptr); + + EditorNode::get_singleton()->remove_control_from_dock(version_commit_dock); + EditorNode::get_singleton()->remove_bottom_panel_item(version_control_dock); -const String VersionControlEditorPlugin::get_vcs_name() const { - return EditorVCSInterface::get_singleton() ? EditorVCSInterface::get_singleton()->get_vcs_name() : ""; + _set_vcs_ui_state(false); } VersionControlEditorPlugin::VersionControlEditorPlugin() { singleton = this; - staged_files_count = 0; version_control_actions = memnew(PopupMenu); metadata_dialog = memnew(ConfirmationDialog); metadata_dialog->set_title(TTR("Create Version Control Metadata")); metadata_dialog->set_min_size(Size2(200, 40)); - version_control_actions->add_child(metadata_dialog); + metadata_dialog->get_ok_button()->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_create_vcs_metadata_files)); + add_child(metadata_dialog); VBoxContainer *metadata_vb = memnew(VBoxContainer); + metadata_dialog->add_child(metadata_vb); + HBoxContainer *metadata_hb = memnew(HBoxContainer); metadata_hb->set_custom_minimum_size(Size2(200, 20)); + metadata_vb->add_child(metadata_hb); + Label *l = memnew(Label); l->set_text(TTR("Create VCS metadata files for:")); metadata_hb->add_child(l); + metadata_selection = memnew(OptionButton); metadata_selection->set_custom_minimum_size(Size2(100, 20)); metadata_selection->add_item("None", (int)EditorVCSInterface::VCSMetadata::NONE); metadata_selection->add_item("Git", (int)EditorVCSInterface::VCSMetadata::GIT); metadata_selection->select((int)EditorVCSInterface::VCSMetadata::GIT); - metadata_dialog->get_ok_button()->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_create_vcs_metadata_files)); metadata_hb->add_child(metadata_selection); - metadata_vb->add_child(metadata_hb); + l = memnew(Label); l->set_text(TTR("Existing VCS metadata files will be overwritten.")); metadata_vb->add_child(l); - metadata_dialog->add_child(metadata_vb); set_up_dialog = memnew(AcceptDialog); - set_up_dialog->set_title(TTR("Set Up Version Control")); - set_up_dialog->set_min_size(Size2(400, 100)); - version_control_actions->add_child(set_up_dialog); + set_up_dialog->set_title(TTR("Local Settings")); + set_up_dialog->set_min_size(Size2(600, 100)); + set_up_dialog->add_cancel_button("Cancel"); + set_up_dialog->set_hide_on_ok(true); + add_child(set_up_dialog); - set_up_ok_button = set_up_dialog->get_ok_button(); - set_up_ok_button->set_text(TTR("Close")); + Button *set_up_apply_button = set_up_dialog->get_ok_button(); + set_up_apply_button->set_text(TTR("Apply")); + set_up_apply_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_set_credentials)); set_up_vbc = memnew(VBoxContainer); set_up_vbc->set_alignment(BoxContainer::ALIGNMENT_CENTER); set_up_dialog->add_child(set_up_vbc); - set_up_hbc = memnew(HBoxContainer); - set_up_hbc->set_h_size_flags(BoxContainer::SIZE_EXPAND_FILL); + HBoxContainer *set_up_hbc = memnew(HBoxContainer); + set_up_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); set_up_vbc->add_child(set_up_hbc); - set_up_vcs_status = memnew(RichTextLabel); - set_up_vcs_status->set_text(TTR("VCS Addon is not initialized")); - set_up_vbc->add_child(set_up_vcs_status); - - set_up_vcs_label = memnew(Label); - set_up_vcs_label->set_text(TTR("Version Control System")); + Label *set_up_vcs_label = memnew(Label); + set_up_vcs_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_vcs_label->set_text(TTR("VCS Provider")); set_up_hbc->add_child(set_up_vcs_label); set_up_choice = memnew(OptionButton); - set_up_choice->set_h_size_flags(HBoxContainer::SIZE_EXPAND_FILL); - set_up_choice->connect("item_selected", callable_mp(this, &VersionControlEditorPlugin::_selected_a_vcs)); + set_up_choice->set_h_size_flags(Control::SIZE_EXPAND_FILL); set_up_hbc->add_child(set_up_choice); - set_up_init_settings = nullptr; - - set_up_init_button = memnew(Button); - set_up_init_button->set_text(TTR("Initialize")); - set_up_init_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_initialize_vcs)); - set_up_vbc->add_child(set_up_init_button); + HBoxContainer *toggle_vcs_hbc = memnew(HBoxContainer); + toggle_vcs_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_vbc->add_child(toggle_vcs_hbc); + + Label *toggle_vcs_label = memnew(Label); + toggle_vcs_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + toggle_vcs_label->set_text(TTR("Connect to VCS")); + toggle_vcs_hbc->add_child(toggle_vcs_label); + + toggle_vcs_choice = memnew(CheckButton); + toggle_vcs_choice->set_h_size_flags(Control::SIZE_EXPAND_FILL); + toggle_vcs_choice->set_pressed_no_signal(false); + toggle_vcs_choice->connect(SNAME("toggled"), callable_mp(this, &VersionControlEditorPlugin::_toggle_vcs_integration)); + toggle_vcs_hbc->add_child(toggle_vcs_choice); + + set_up_vbc->add_child(memnew(HSeparator)); + + set_up_settings_vbc = memnew(VBoxContainer); + set_up_settings_vbc->set_alignment(BoxContainer::ALIGNMENT_CENTER); + set_up_vbc->add_child(set_up_settings_vbc); + + Label *remote_login = memnew(Label); + remote_login->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_login->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + remote_login->set_text(TTR("Remote Login")); + set_up_settings_vbc->add_child(remote_login); + + HBoxContainer *set_up_username_input = memnew(HBoxContainer); + set_up_username_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_settings_vbc->add_child(set_up_username_input); + + Label *set_up_username_label = memnew(Label); + set_up_username_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_username_label->set_text(TTR("Username")); + set_up_username_input->add_child(set_up_username_label); + + set_up_username = memnew(LineEdit); + set_up_username->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_username->set_text(EDITOR_DEF("version_control/username", "")); + set_up_username->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_set_up_warning)); + set_up_username_input->add_child(set_up_username); + + HBoxContainer *set_up_password_input = memnew(HBoxContainer); + set_up_password_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_settings_vbc->add_child(set_up_password_input); + + Label *set_up_password_label = memnew(Label); + set_up_password_label->set_text(TTR("Password")); + set_up_password_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_password_input->add_child(set_up_password_label); + + set_up_password = memnew(LineEdit); + set_up_password->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_password->set_secret(true); + set_up_password->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_set_up_warning)); + set_up_password_input->add_child(set_up_password); + + const String home_dir = OS::get_singleton()->has_environment("HOME") ? OS::get_singleton()->get_environment("HOME") : OS::get_singleton()->get_system_dir(OS::SYSTEM_DIR_DOCUMENTS); + + HBoxContainer *set_up_ssh_public_key_input = memnew(HBoxContainer); + set_up_ssh_public_key_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_settings_vbc->add_child(set_up_ssh_public_key_input); + + Label *set_up_ssh_public_key_label = memnew(Label); + set_up_ssh_public_key_label->set_text(TTR("SSH Public Key Path")); + set_up_ssh_public_key_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_public_key_input->add_child(set_up_ssh_public_key_label); + + HBoxContainer *set_up_ssh_public_key_input_hbc = memnew(HBoxContainer); + set_up_ssh_public_key_input_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_public_key_input->add_child(set_up_ssh_public_key_input_hbc); + + set_up_ssh_public_key_path = memnew(LineEdit); + set_up_ssh_public_key_path->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_public_key_path->set_text(EDITOR_DEF("version_control/ssh_public_key_path", "")); + set_up_ssh_public_key_path->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_set_up_warning)); + set_up_ssh_public_key_input_hbc->add_child(set_up_ssh_public_key_path); + + set_up_ssh_public_key_file_dialog = memnew(FileDialog); + set_up_ssh_public_key_file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM); + set_up_ssh_public_key_file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE); + set_up_ssh_public_key_file_dialog->set_show_hidden_files(true); + set_up_ssh_public_key_file_dialog->set_current_dir(home_dir); + set_up_ssh_public_key_file_dialog->connect(SNAME("file_selected"), callable_mp(this, &VersionControlEditorPlugin::_ssh_public_key_selected)); + set_up_ssh_public_key_input_hbc->add_child(set_up_ssh_public_key_file_dialog); + + Button *select_public_path_button = memnew(Button); + select_public_path_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Folder", "EditorIcons")); + select_public_path_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_popup_file_dialog).bind(set_up_ssh_public_key_file_dialog)); + select_public_path_button->set_tooltip_text(TTR("Select SSH public key path")); + set_up_ssh_public_key_input_hbc->add_child(select_public_path_button); + + HBoxContainer *set_up_ssh_private_key_input = memnew(HBoxContainer); + set_up_ssh_private_key_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_settings_vbc->add_child(set_up_ssh_private_key_input); + + Label *set_up_ssh_private_key_label = memnew(Label); + set_up_ssh_private_key_label->set_text(TTR("SSH Private Key Path")); + set_up_ssh_private_key_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_private_key_input->add_child(set_up_ssh_private_key_label); + + HBoxContainer *set_up_ssh_private_key_input_hbc = memnew(HBoxContainer); + set_up_ssh_private_key_input_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_private_key_input->add_child(set_up_ssh_private_key_input_hbc); + + set_up_ssh_private_key_path = memnew(LineEdit); + set_up_ssh_private_key_path->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_private_key_path->set_text(EDITOR_DEF("version_control/ssh_private_key_path", "")); + set_up_ssh_private_key_path->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_set_up_warning)); + set_up_ssh_private_key_input_hbc->add_child(set_up_ssh_private_key_path); + + set_up_ssh_private_key_file_dialog = memnew(FileDialog); + set_up_ssh_private_key_file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM); + set_up_ssh_private_key_file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE); + set_up_ssh_private_key_file_dialog->set_show_hidden_files(true); + set_up_ssh_private_key_file_dialog->set_current_dir(home_dir); + set_up_ssh_private_key_file_dialog->connect("file_selected", callable_mp(this, &VersionControlEditorPlugin::_ssh_private_key_selected)); + set_up_ssh_private_key_input_hbc->add_child(set_up_ssh_private_key_file_dialog); + + Button *select_private_path_button = memnew(Button); + select_private_path_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Folder", "EditorIcons")); + select_private_path_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_popup_file_dialog).bind(set_up_ssh_private_key_file_dialog)); + select_private_path_button->set_tooltip_text(TTR("Select SSH private key path")); + set_up_ssh_private_key_input_hbc->add_child(select_private_path_button); + + HBoxContainer *set_up_ssh_passphrase_input = memnew(HBoxContainer); + set_up_ssh_passphrase_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_settings_vbc->add_child(set_up_ssh_passphrase_input); + + Label *set_up_ssh_passphrase_label = memnew(Label); + set_up_ssh_passphrase_label->set_text(TTR("SSH Passphrase")); + set_up_ssh_passphrase_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_passphrase_input->add_child(set_up_ssh_passphrase_label); + + set_up_ssh_passphrase = memnew(LineEdit); + set_up_ssh_passphrase->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_passphrase->set_secret(true); + set_up_ssh_passphrase->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_set_up_warning)); + set_up_ssh_passphrase_input->add_child(set_up_ssh_passphrase); + + set_up_warning_text = memnew(Label); + set_up_warning_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + set_up_warning_text->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_settings_vbc->add_child(set_up_warning_text); version_commit_dock = memnew(VBoxContainer); version_commit_dock->set_visible(false); + version_commit_dock->set_name(TTR("Commit")); - commit_box_vbc = memnew(VBoxContainer); - commit_box_vbc->set_alignment(VBoxContainer::ALIGNMENT_BEGIN); - commit_box_vbc->set_h_size_flags(VBoxContainer::SIZE_EXPAND_FILL); - commit_box_vbc->set_v_size_flags(VBoxContainer::SIZE_EXPAND_FILL); - version_commit_dock->add_child(commit_box_vbc); + VBoxContainer *unstage_area = memnew(VBoxContainer); + unstage_area->set_v_size_flags(Control::SIZE_EXPAND_FILL); + unstage_area->set_h_size_flags(Control::SIZE_EXPAND_FILL); + version_commit_dock->add_child(unstage_area); - stage_tools = memnew(HSplitContainer); - stage_tools->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN_COLLAPSED); - commit_box_vbc->add_child(stage_tools); + HBoxContainer *unstage_title = memnew(HBoxContainer); + unstage_area->add_child(unstage_title); - staging_area_label = memnew(Label); - staging_area_label->set_h_size_flags(Label::SIZE_EXPAND_FILL); - staging_area_label->set_text(TTR("Staging area")); - stage_tools->add_child(staging_area_label); + Label *unstage_label = memnew(Label); + unstage_label->set_text(TTR("Unstaged Changes")); + unstage_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + unstage_title->add_child(unstage_label); refresh_button = memnew(Button); - refresh_button->set_tooltip(TTR("Detect new changes")); - refresh_button->set_text(TTR("Refresh")); + refresh_button->set_tooltip_text(TTR("Detect new changes")); + refresh_button->set_flat(true); refresh_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); - refresh_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); - stage_tools->add_child(refresh_button); - - stage_files = memnew(Tree); - stage_files->set_h_size_flags(Tree::SIZE_EXPAND_FILL); - stage_files->set_v_size_flags(Tree::SIZE_EXPAND_FILL); - stage_files->set_columns(1); - stage_files->set_column_title(0, TTR("Changes")); - stage_files->set_column_titles_visible(true); - stage_files->set_allow_reselect(true); - stage_files->set_allow_rmb_select(true); - stage_files->set_select_mode(Tree::SelectMode::SELECT_MULTI); - stage_files->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true); - stage_files->connect("cell_selected", callable_mp(this, &VersionControlEditorPlugin::_view_file_diff)); - stage_files->create_item(); - stage_files->set_hide_root(true); - commit_box_vbc->add_child(stage_files); - - change_type_to_strings[CHANGE_TYPE_NEW] = TTR("New"); - change_type_to_strings[CHANGE_TYPE_MODIFIED] = TTR("Modified"); - change_type_to_strings[CHANGE_TYPE_RENAMED] = TTR("Renamed"); - change_type_to_strings[CHANGE_TYPE_DELETED] = TTR("Deleted"); - change_type_to_strings[CHANGE_TYPE_TYPECHANGE] = TTR("Typechange"); - - change_type_to_color[CHANGE_TYPE_NEW] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor")); - change_type_to_color[CHANGE_TYPE_MODIFIED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor")); - change_type_to_color[CHANGE_TYPE_RENAMED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("disabled_font_color"), SNAME("Editor")); - change_type_to_color[CHANGE_TYPE_DELETED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")); - change_type_to_color[CHANGE_TYPE_TYPECHANGE] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Editor")); - - stage_buttons = memnew(HSplitContainer); - stage_buttons->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN_COLLAPSED); - commit_box_vbc->add_child(stage_buttons); - - stage_selected_button = memnew(Button); - stage_selected_button->set_h_size_flags(Button::SIZE_EXPAND_FILL); - stage_selected_button->set_text(TTR("Stage Selected")); - stage_selected_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_stage_selected)); - stage_buttons->add_child(stage_selected_button); + refresh_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); + refresh_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_commit_list)); + refresh_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_branch_list)); + refresh_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_remote_list)); + unstage_title->add_child(refresh_button); + + discard_all_confirm = memnew(AcceptDialog); + discard_all_confirm->set_title(TTR("Discard all changes")); + discard_all_confirm->set_min_size(Size2i(400, 50)); + discard_all_confirm->set_text(TTR("This operation is IRREVERSIBLE. Your changes will be deleted FOREVER.")); + discard_all_confirm->set_hide_on_ok(true); + discard_all_confirm->set_ok_button_text(TTR("Permanentally delete my changes")); + discard_all_confirm->add_cancel_button(); + version_commit_dock->add_child(discard_all_confirm); + + discard_all_confirm->get_ok_button()->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_discard_all)); + + discard_all_button = memnew(Button); + discard_all_button->set_tooltip_text(TTR("Discard all changes")); + discard_all_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Close"), SNAME("EditorIcons"))); + discard_all_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_confirm_discard_all)); + discard_all_button->set_flat(true); + unstage_title->add_child(discard_all_button); stage_all_button = memnew(Button); - stage_all_button->set_text(TTR("Stage All")); - stage_all_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_stage_all)); - stage_buttons->add_child(stage_all_button); - - commit_box_vbc->add_child(memnew(HSeparator)); + stage_all_button->set_flat(true); + stage_all_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MoveDown"), SNAME("EditorIcons"))); + stage_all_button->set_tooltip_text(TTR("Stage all changes")); + unstage_title->add_child(stage_all_button); + + unstaged_files = memnew(Tree); + unstaged_files->set_h_size_flags(Tree::SIZE_EXPAND_FILL); + unstaged_files->set_v_size_flags(Tree::SIZE_EXPAND_FILL); + unstaged_files->set_select_mode(Tree::SELECT_ROW); + unstaged_files->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_load_diff).bind(unstaged_files)); + unstaged_files->connect(SNAME("item_activated"), callable_mp(this, &VersionControlEditorPlugin::_item_activated).bind(unstaged_files)); + unstaged_files->connect(SNAME("button_clicked"), callable_mp(this, &VersionControlEditorPlugin::_cell_button_pressed)); + unstaged_files->create_item(); + unstaged_files->set_hide_root(true); + unstage_area->add_child(unstaged_files); + + VBoxContainer *stage_area = memnew(VBoxContainer); + stage_area->set_v_size_flags(Control::SIZE_EXPAND_FILL); + stage_area->set_h_size_flags(Control::SIZE_EXPAND_FILL); + version_commit_dock->add_child(stage_area); + + HBoxContainer *stage_title = memnew(HBoxContainer); + stage_area->add_child(stage_title); + + Label *stage_label = memnew(Label); + stage_label->set_text(TTR("Staged Changes")); + stage_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + stage_title->add_child(stage_label); + + unstage_all_button = memnew(Button); + unstage_all_button->set_flat(true); + unstage_all_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MoveUp"), SNAME("EditorIcons"))); + unstage_all_button->set_tooltip_text(TTR("Unstage all changes")); + stage_title->add_child(unstage_all_button); + + staged_files = memnew(Tree); + staged_files->set_h_size_flags(Tree::SIZE_EXPAND_FILL); + staged_files->set_v_size_flags(Tree::SIZE_EXPAND_FILL); + staged_files->set_select_mode(Tree::SELECT_ROW); + staged_files->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_load_diff).bind(staged_files)); + staged_files->connect(SNAME("button_clicked"), callable_mp(this, &VersionControlEditorPlugin::_cell_button_pressed)); + staged_files->connect(SNAME("item_activated"), callable_mp(this, &VersionControlEditorPlugin::_item_activated).bind(staged_files)); + staged_files->create_item(); + staged_files->set_hide_root(true); + stage_area->add_child(staged_files); + + // Editor crashes if bind is null + unstage_all_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_move_all).bind(staged_files)); + stage_all_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_move_all).bind(unstaged_files)); + + version_commit_dock->add_child(memnew(HSeparator)); + + VBoxContainer *commit_area = memnew(VBoxContainer); + version_commit_dock->add_child(commit_area); + + Label *commit_label = memnew(Label); + commit_label->set_text(TTR("Commit Message")); + commit_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + commit_area->add_child(commit_label); commit_message = memnew(TextEdit); commit_message->set_h_size_flags(Control::SIZE_EXPAND_FILL); commit_message->set_h_grow_direction(Control::GrowDirection::GROW_DIRECTION_BEGIN); commit_message->set_v_grow_direction(Control::GrowDirection::GROW_DIRECTION_END); commit_message->set_custom_minimum_size(Size2(200, 100)); - commit_message->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); - commit_message->connect("text_changed", callable_mp(this, &VersionControlEditorPlugin::_update_commit_button)); - commit_message->connect("gui_input", callable_mp(this, &VersionControlEditorPlugin::_commit_message_gui_input)); - commit_box_vbc->add_child(commit_message); - ED_SHORTCUT("version_control/commit", TTR("Commit"), KeyModifierMask::CMD | Key::ENTER); + commit_message->set_line_wrapping_mode(TextEdit::LINE_WRAPPING_BOUNDARY); + commit_message->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_commit_button)); + commit_message->connect(SNAME("gui_input"), callable_mp(this, &VersionControlEditorPlugin::_commit_message_gui_input)); + commit_area->add_child(commit_message); + + ED_SHORTCUT("version_control/commit", TTR("Commit"), KeyModifierMask::CMD_OR_CTRL | Key::ENTER); commit_button = memnew(Button); commit_button->set_text(TTR("Commit Changes")); commit_button->set_disabled(true); - commit_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_send_commit_msg)); - commit_box_vbc->add_child(commit_button); - - commit_status = memnew(Label); - commit_status->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); - commit_box_vbc->add_child(commit_status); - - version_control_dock = memnew(PanelContainer); + commit_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_commit)); + commit_area->add_child(commit_button); + + version_commit_dock->add_child(memnew(HSeparator)); + + HBoxContainer *commit_list_hbc = memnew(HBoxContainer); + version_commit_dock->add_child(commit_list_hbc); + + Label *commit_list_label = memnew(Label); + commit_list_label->set_text(TTR("Commit List")); + commit_list_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + commit_list_hbc->add_child(commit_list_label); + + commit_list_size_button = memnew(OptionButton); + commit_list_size_button->set_tooltip_text(TTR("Commit list size")); + commit_list_size_button->add_item("10"); + commit_list_size_button->set_item_metadata(0, 10); + commit_list_size_button->add_item("20"); + commit_list_size_button->set_item_metadata(1, 20); + commit_list_size_button->add_item("30"); + commit_list_size_button->set_item_metadata(2, 30); + commit_list_size_button->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_set_commit_list_size)); + commit_list_hbc->add_child(commit_list_size_button); + + commit_list = memnew(Tree); + commit_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); + commit_list->set_v_grow_direction(Control::GrowDirection::GROW_DIRECTION_END); + commit_list->set_custom_minimum_size(Size2(200, 160)); + commit_list->create_item(); + commit_list->set_hide_root(true); + commit_list->set_select_mode(Tree::SELECT_ROW); + commit_list->set_columns(2); // Commit msg, author + commit_list->set_column_custom_minimum_width(0, 40); + commit_list->set_column_custom_minimum_width(1, 20); + commit_list->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_load_diff).bind(commit_list)); + version_commit_dock->add_child(commit_list); + + version_commit_dock->add_child(memnew(HSeparator)); + + HBoxContainer *menu_bar = memnew(HBoxContainer); + menu_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL); + menu_bar->set_v_size_flags(Control::SIZE_FILL); + version_commit_dock->add_child(menu_bar); + + branch_select = memnew(OptionButton); + branch_select->set_tooltip_text(TTR("Branches")); + branch_select->set_h_size_flags(Control::SIZE_EXPAND_FILL); + branch_select->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_branch_item_selected)); + branch_select->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_branch_list)); + menu_bar->add_child(branch_select); + + branch_create_confirm = memnew(AcceptDialog); + branch_create_confirm->set_title(TTR("Create New Branch")); + branch_create_confirm->set_min_size(Size2(400, 100)); + branch_create_confirm->set_hide_on_ok(true); + version_commit_dock->add_child(branch_create_confirm); + + branch_create_ok = branch_create_confirm->get_ok_button(); + branch_create_ok->set_text(TTR("Create")); + branch_create_ok->set_disabled(true); + branch_create_ok->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_create_branch)); + + branch_remove_confirm = memnew(AcceptDialog); + branch_remove_confirm->set_title(TTR("Remove Branch")); + branch_remove_confirm->add_cancel_button(); + version_commit_dock->add_child(branch_remove_confirm); + + Button *branch_remove_ok = branch_remove_confirm->get_ok_button(); + branch_remove_ok->set_text(TTR("Remove")); + branch_remove_ok->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_remove_branch)); + + VBoxContainer *branch_create_vbc = memnew(VBoxContainer); + branch_create_vbc->set_alignment(BoxContainer::ALIGNMENT_CENTER); + branch_create_confirm->add_child(branch_create_vbc); + + HBoxContainer *branch_create_hbc = memnew(HBoxContainer); + branch_create_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); + branch_create_vbc->add_child(branch_create_hbc); + + Label *branch_create_name_label = memnew(Label); + branch_create_name_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + branch_create_name_label->set_text(TTR("Branch Name")); + branch_create_hbc->add_child(branch_create_name_label); + + branch_create_name_input = memnew(LineEdit); + branch_create_name_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + branch_create_name_input->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_branch_create_button)); + branch_create_hbc->add_child(branch_create_name_input); + + remote_select = memnew(OptionButton); + remote_select->set_tooltip_text(TTR("Remotes")); + remote_select->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_select->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_remote_selected)); + remote_select->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_remote_list)); + menu_bar->add_child(remote_select); + + remote_create_confirm = memnew(AcceptDialog); + remote_create_confirm->set_title(TTR("Create New Remote")); + remote_create_confirm->set_min_size(Size2(400, 100)); + remote_create_confirm->set_hide_on_ok(true); + version_commit_dock->add_child(remote_create_confirm); + + remote_create_ok = remote_create_confirm->get_ok_button(); + remote_create_ok->set_text(TTR("Create")); + remote_create_ok->set_disabled(true); + remote_create_ok->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_create_remote)); + + remote_remove_confirm = memnew(AcceptDialog); + remote_remove_confirm->set_title(TTR("Remove Remote")); + remote_remove_confirm->add_cancel_button(); + version_commit_dock->add_child(remote_remove_confirm); + + Button *remote_remove_ok = remote_remove_confirm->get_ok_button(); + remote_remove_ok->set_text(TTR("Remove")); + remote_remove_ok->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_remove_remote)); + + VBoxContainer *remote_create_vbc = memnew(VBoxContainer); + remote_create_vbc->set_alignment(BoxContainer::ALIGNMENT_CENTER); + remote_create_confirm->add_child(remote_create_vbc); + + HBoxContainer *remote_create_name_hbc = memnew(HBoxContainer); + remote_create_name_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_create_vbc->add_child(remote_create_name_hbc); + + Label *remote_create_name_label = memnew(Label); + remote_create_name_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_create_name_label->set_text(TTR("Remote Name")); + remote_create_name_hbc->add_child(remote_create_name_label); + + remote_create_name_input = memnew(LineEdit); + remote_create_name_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_create_name_input->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_remote_create_button)); + remote_create_name_hbc->add_child(remote_create_name_input); + + HBoxContainer *remote_create_hbc = memnew(HBoxContainer); + remote_create_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_create_vbc->add_child(remote_create_hbc); + + Label *remote_create_url_label = memnew(Label); + remote_create_url_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_create_url_label->set_text(TTR("Remote URL")); + remote_create_hbc->add_child(remote_create_url_label); + + remote_create_url_input = memnew(LineEdit); + remote_create_url_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_create_url_input->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_remote_create_button)); + remote_create_hbc->add_child(remote_create_url_input); + + fetch_button = memnew(Button); + fetch_button->set_flat(true); + fetch_button->set_tooltip_text(TTR("Fetch")); + fetch_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); + fetch_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_fetch)); + menu_bar->add_child(fetch_button); + + pull_button = memnew(Button); + pull_button->set_flat(true); + pull_button->set_tooltip_text(TTR("Pull")); + pull_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MoveDown"), SNAME("EditorIcons"))); + pull_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_pull)); + menu_bar->add_child(pull_button); + + push_button = memnew(Button); + push_button->set_flat(true); + push_button->set_tooltip_text(TTR("Push")); + push_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MoveUp"), SNAME("EditorIcons"))); + push_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_push)); + menu_bar->add_child(push_button); + + extra_options = memnew(MenuButton); + extra_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + extra_options->get_popup()->connect(SNAME("about_to_popup"), callable_mp(this, &VersionControlEditorPlugin::_update_extra_options)); + extra_options->get_popup()->connect(SNAME("id_pressed"), callable_mp(this, &VersionControlEditorPlugin::_extra_option_selected)); + menu_bar->add_child(extra_options); + + extra_options->get_popup()->add_item(TTR("Force Push"), EXTRA_OPTION_FORCE_PUSH); + extra_options->get_popup()->add_separator(); + extra_options->get_popup()->add_item(TTR("Create New Branch"), EXTRA_OPTION_CREATE_BRANCH); + + extra_options_remove_branch_list = memnew(PopupMenu); + extra_options_remove_branch_list->connect(SNAME("id_pressed"), callable_mp(this, &VersionControlEditorPlugin::_popup_branch_remove_confirm)); + extra_options_remove_branch_list->set_name("Remove Branch"); + extra_options->get_popup()->add_child(extra_options_remove_branch_list); + extra_options->get_popup()->add_submenu_item(TTR("Remove Branch"), "Remove Branch"); + + extra_options->get_popup()->add_separator(); + extra_options->get_popup()->add_item(TTR("Create New Remote"), EXTRA_OPTION_CREATE_REMOTE); + + extra_options_remove_remote_list = memnew(PopupMenu); + extra_options_remove_remote_list->connect(SNAME("id_pressed"), callable_mp(this, &VersionControlEditorPlugin::_popup_remote_remove_confirm)); + extra_options_remove_remote_list->set_name("Remove Remote"); + extra_options->get_popup()->add_child(extra_options_remove_remote_list); + extra_options->get_popup()->add_submenu_item(TTR("Remove Remote"), "Remove Remote"); + + change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_NEW] = TTR("New"); + change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_MODIFIED] = TTR("Modified"); + change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_RENAMED] = TTR("Renamed"); + change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_DELETED] = TTR("Deleted"); + change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_TYPECHANGE] = TTR("Typechange"); + change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_UNMERGED] = TTR("Unmerged"); + + change_type_to_color[EditorVCSInterface::CHANGE_TYPE_NEW] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor")); + change_type_to_color[EditorVCSInterface::CHANGE_TYPE_MODIFIED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor")); + change_type_to_color[EditorVCSInterface::CHANGE_TYPE_RENAMED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor")); + change_type_to_color[EditorVCSInterface::CHANGE_TYPE_DELETED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")); + change_type_to_color[EditorVCSInterface::CHANGE_TYPE_TYPECHANGE] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Editor")); + change_type_to_color[EditorVCSInterface::CHANGE_TYPE_UNMERGED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor")); + + change_type_to_icon[EditorVCSInterface::CHANGE_TYPE_NEW] = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusSuccess"), SNAME("EditorIcons")); + change_type_to_icon[EditorVCSInterface::CHANGE_TYPE_MODIFIED] = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); + change_type_to_icon[EditorVCSInterface::CHANGE_TYPE_RENAMED] = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); + change_type_to_icon[EditorVCSInterface::CHANGE_TYPE_TYPECHANGE] = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); + change_type_to_icon[EditorVCSInterface::CHANGE_TYPE_DELETED] = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusError"), SNAME("EditorIcons")); + change_type_to_icon[EditorVCSInterface::CHANGE_TYPE_UNMERGED] = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); + + version_control_dock = memnew(VBoxContainer); version_control_dock->set_v_size_flags(Control::SIZE_EXPAND_FILL); version_control_dock->set_custom_minimum_size(Size2(0, 300) * EDSCALE); version_control_dock->hide(); - diff_vbc = memnew(VBoxContainer); - diff_vbc->set_h_size_flags(HBoxContainer::SIZE_FILL); - diff_vbc->set_v_size_flags(HBoxContainer::SIZE_FILL); - version_control_dock->add_child(diff_vbc); + HBoxContainer *diff_heading = memnew(HBoxContainer); + diff_heading->set_h_size_flags(Control::SIZE_EXPAND_FILL); + diff_heading->set_tooltip_text(TTR("View file diffs before committing them to the latest version")); + version_control_dock->add_child(diff_heading); - diff_hbc = memnew(HBoxContainer); - diff_hbc->set_h_size_flags(HBoxContainer::SIZE_FILL); - diff_vbc->add_child(diff_hbc); + diff_title = memnew(Label); + diff_title->set_h_size_flags(Control::SIZE_EXPAND_FILL); + diff_heading->add_child(diff_title); - diff_heading = memnew(Label); - diff_heading->set_text(TTR("Status")); - diff_heading->set_tooltip(TTR("View file diffs before committing them to the latest version")); - diff_hbc->add_child(diff_heading); + Label *view = memnew(Label); + view->set_text(TTR("View:")); + diff_heading->add_child(view); - diff_file_name = memnew(Label); - diff_file_name->set_text(TTR("No file diff is active")); - diff_file_name->set_h_size_flags(Label::SIZE_EXPAND_FILL); - diff_file_name->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); - diff_hbc->add_child(diff_file_name); - - diff_refresh_button = memnew(Button); - diff_refresh_button->set_tooltip(TTR("Detect changes in file diff")); - diff_refresh_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); - diff_refresh_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_refresh_file_diff)); - diff_hbc->add_child(diff_refresh_button); + diff_view_type_select = memnew(OptionButton); + diff_view_type_select->add_item(TTR("Split"), DIFF_VIEW_TYPE_SPLIT); + diff_view_type_select->add_item(TTR("Unified"), DIFF_VIEW_TYPE_UNIFIED); + diff_view_type_select->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_display_diff)); + diff_heading->add_child(diff_view_type_select); diff = memnew(RichTextLabel); diff->set_h_size_flags(TextEdit::SIZE_EXPAND_FILL); diff->set_v_size_flags(TextEdit::SIZE_EXPAND_FILL); + diff->set_use_bbcode(true); diff->set_selection_enabled(true); - diff_vbc->add_child(diff); + version_control_dock->add_child(diff); + + _update_set_up_warning(""); } VersionControlEditorPlugin::~VersionControlEditorPlugin() { shut_down(); - memdelete(version_control_dock); memdelete(version_commit_dock); + memdelete(version_control_dock); memdelete(version_control_actions); } diff --git a/editor/plugins/version_control_editor_plugin.h b/editor/plugins/version_control_editor_plugin.h index 86f98ad3aa..ca55b86578 100644 --- a/editor/plugins/version_control_editor_plugin.h +++ b/editor/plugins/version_control_editor_plugin.h @@ -1,40 +1,44 @@ -/*************************************************************************/ -/* version_control_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* version_control_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 VERSION_CONTROL_EDITOR_PLUGIN_H #define VERSION_CONTROL_EDITOR_PLUGIN_H #include "editor/editor_plugin.h" #include "editor/editor_vcs_interface.h" +#include "scene/gui/check_button.h" #include "scene/gui/container.h" +#include "scene/gui/file_dialog.h" +#include "scene/gui/menu_button.h" #include "scene/gui/rich_text_label.h" +#include "scene/gui/tab_container.h" #include "scene/gui/text_edit.h" #include "scene/gui/tree.h" @@ -42,79 +46,154 @@ class VersionControlEditorPlugin : public EditorPlugin { GDCLASS(VersionControlEditorPlugin, EditorPlugin) public: - enum ChangeType { - CHANGE_TYPE_NEW = 0, - CHANGE_TYPE_MODIFIED = 1, - CHANGE_TYPE_RENAMED = 2, - CHANGE_TYPE_DELETED = 3, - CHANGE_TYPE_TYPECHANGE = 4 + enum ButtonType { + BUTTON_TYPE_OPEN = 0, + BUTTON_TYPE_DISCARD = 1, + }; + + enum DiffViewType { + DIFF_VIEW_TYPE_SPLIT = 0, + DIFF_VIEW_TYPE_UNIFIED = 1, + }; + + enum ExtraOption { + EXTRA_OPTION_FORCE_PUSH, + EXTRA_OPTION_CREATE_BRANCH, + EXTRA_OPTION_CREATE_REMOTE, }; private: static VersionControlEditorPlugin *singleton; - int staged_files_count; - List<StringName> available_addons; - - PopupMenu *version_control_actions; - ConfirmationDialog *metadata_dialog; - OptionButton *metadata_selection; - AcceptDialog *set_up_dialog; - VBoxContainer *set_up_vbc; - HBoxContainer *set_up_hbc; - Label *set_up_vcs_label; - OptionButton *set_up_choice; - PanelContainer *set_up_init_settings; - Button *set_up_init_button; - RichTextLabel *set_up_vcs_status; - Button *set_up_ok_button; - - HashMap<ChangeType, String> change_type_to_strings; - HashMap<ChangeType, Color> change_type_to_color; - - VBoxContainer *version_commit_dock; - VBoxContainer *commit_box_vbc; - HSplitContainer *stage_tools; - Tree *stage_files; - TreeItem *new_files; - TreeItem *modified_files; - TreeItem *renamed_files; - TreeItem *deleted_files; - TreeItem *typechange_files; - Label *staging_area_label; - HSplitContainer *stage_buttons; - Button *stage_all_button; - Button *stage_selected_button; - Button *refresh_button; - TextEdit *commit_message; - Button *commit_button; - Label *commit_status; - - PanelContainer *version_control_dock; - Button *version_control_dock_button; - VBoxContainer *diff_vbc; - HBoxContainer *diff_hbc; - Button *diff_refresh_button; - Label *diff_file_name; - Label *diff_heading; - RichTextLabel *diff; - - void _populate_available_vcs_names(); - void _create_vcs_metadata_files(); - void _selected_a_vcs(int p_id); + List<StringName> available_plugins; + + PopupMenu *version_control_actions = nullptr; + ConfirmationDialog *metadata_dialog = nullptr; + OptionButton *metadata_selection = nullptr; + AcceptDialog *set_up_dialog = nullptr; + CheckButton *toggle_vcs_choice = nullptr; + OptionButton *set_up_choice = nullptr; + VBoxContainer *set_up_vbc = nullptr; + VBoxContainer *set_up_settings_vbc = nullptr; + LineEdit *set_up_username = nullptr; + LineEdit *set_up_password = nullptr; + LineEdit *set_up_ssh_public_key_path = nullptr; + LineEdit *set_up_ssh_private_key_path = nullptr; + LineEdit *set_up_ssh_passphrase = nullptr; + FileDialog *set_up_ssh_public_key_file_dialog = nullptr; + FileDialog *set_up_ssh_private_key_file_dialog = nullptr; + Label *set_up_warning_text = nullptr; + + AcceptDialog *discard_all_confirm = nullptr; + + OptionButton *commit_list_size_button = nullptr; + + AcceptDialog *branch_create_confirm = nullptr; + LineEdit *branch_create_name_input = nullptr; + Button *branch_create_ok = nullptr; + + AcceptDialog *remote_create_confirm = nullptr; + LineEdit *remote_create_name_input = nullptr; + LineEdit *remote_create_url_input = nullptr; + Button *remote_create_ok = nullptr; + + HashMap<EditorVCSInterface::ChangeType, String> change_type_to_strings; + HashMap<EditorVCSInterface::ChangeType, Color> change_type_to_color; + HashMap<EditorVCSInterface::ChangeType, Ref<Texture>> change_type_to_icon; + + VBoxContainer *version_commit_dock = nullptr; + Tree *staged_files = nullptr; + Tree *unstaged_files = nullptr; + Tree *commit_list = nullptr; + + OptionButton *branch_select = nullptr; + Button *branch_remove_button = nullptr; + AcceptDialog *branch_remove_confirm = nullptr; + + Button *fetch_button = nullptr; + Button *pull_button = nullptr; + Button *push_button = nullptr; + OptionButton *remote_select = nullptr; + Button *remote_remove_button = nullptr; + AcceptDialog *remote_remove_confirm = nullptr; + MenuButton *extra_options = nullptr; + PopupMenu *extra_options_remove_branch_list = nullptr; + PopupMenu *extra_options_remove_remote_list = nullptr; + + String branch_to_remove; + String remote_to_remove; + + Button *stage_all_button = nullptr; + Button *unstage_all_button = nullptr; + Button *discard_all_button = nullptr; + Button *refresh_button = nullptr; + TextEdit *commit_message = nullptr; + Button *commit_button = nullptr; + + VBoxContainer *version_control_dock = nullptr; + Button *version_control_dock_button = nullptr; + Label *diff_title = nullptr; + RichTextLabel *diff = nullptr; + OptionButton *diff_view_type_select = nullptr; + bool show_commit_diff_header = false; + List<EditorVCSInterface::DiffFile> diff_content; + + void _notification(int p_what); void _initialize_vcs(); - void _send_commit_msg(); + void _set_vcs_ui_state(bool p_enabled); + void _set_credentials(); + void _ssh_public_key_selected(String p_path); + void _ssh_private_key_selected(String p_path); + void _populate_available_vcs_names(); + void _update_remotes_list(); + void _update_set_up_warning(String p_new_text); + void _update_opened_tabs(); + void _update_extra_options(); + + bool _load_plugin(String p_name); + + void _pull(); + void _push(); + void _force_push(); + void _fetch(); + void _commit(); + void _confirm_discard_all(); + void _discard_all(); void _refresh_stage_area(); - void _stage_selected(); - void _stage_all(); - void _view_file_diff(); - void _display_file_diff(String p_file_path); - void _refresh_file_diff(); - void _clear_file_diff(); - void _update_stage_status(); - void _update_commit_status(); + void _refresh_branch_list(); + void _set_commit_list_size(int p_index); + void _refresh_commit_list(); + void _refresh_remote_list(); + void _display_diff(int p_idx); + void _move_all(Object *p_tree); + void _load_diff(Object *p_tree); + void _clear_diff(); + int _get_item_count(Tree *p_tree); + void _item_activated(Object *p_tree); + void _create_branch(); + void _create_remote(); + void _update_branch_create_button(String p_new_text); + void _update_remote_create_button(String p_new_text); + void _branch_item_selected(int p_index); + void _remote_selected(int p_index); + void _remove_branch(); + void _remove_remote(); + void _popup_branch_remove_confirm(int p_index); + void _popup_remote_remove_confirm(int p_index); + void _move_item(Tree *p_tree, TreeItem *p_itme); + void _display_diff_split_view(List<EditorVCSInterface::DiffLine> &p_diff_content); + void _display_diff_unified_view(List<EditorVCSInterface::DiffLine> &p_diff_content); + void _discard_file(String p_file_path, EditorVCSInterface::ChangeType p_change); + void _cell_button_pressed(Object *p_item, int p_column, int p_id, int p_mouse_button_index); + void _add_new_item(Tree *p_tree, String p_file_path, EditorVCSInterface::ChangeType p_change); void _update_commit_button(); void _commit_message_gui_input(const Ref<InputEvent> &p_event); + void _extra_option_selected(int p_index); + bool _is_staging_area_empty(); + String _get_date_string_from(int64_t p_unix_timestamp, int64_t p_offset_minutes) const; + void _create_vcs_metadata_files(); + void _popup_file_dialog(Variant p_file_dialog_variant); + void _toggle_vcs_integration(bool p_toggled); friend class EditorVCSInterface; @@ -126,25 +205,15 @@ public: void popup_vcs_metadata_dialog(); void popup_vcs_set_up_dialog(const Control *p_gui_base); - void set_version_control_tool_button(Button *p_button) { version_control_dock_button = p_button; } PopupMenu *get_version_control_actions_panel() const { return version_control_actions; } - VBoxContainer *get_version_commit_dock() const { return version_commit_dock; } - PanelContainer *get_version_control_dock() const { return version_control_dock; } - - List<StringName> get_available_vcs_names() const { return available_addons; } - bool is_vcs_initialized() const; - const String get_vcs_name() const; void register_editor(); - void fetch_available_vcs_addon_names(); - void clear_stage_area(); + void fetch_available_vcs_plugin_names(); void shut_down(); VersionControlEditorPlugin(); ~VersionControlEditorPlugin(); }; -VARIANT_ENUM_CAST(VersionControlEditorPlugin::ChangeType); - -#endif // !VERSION_CONTROL_EDITOR_PLUGIN_H +#endif // VERSION_CONTROL_EDITOR_PLUGIN_H diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index bc68387376..af761a2cea 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -1,51 +1,63 @@ -/*************************************************************************/ -/* visual_shader_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* visual_shader_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 "visual_shader_editor_plugin.h" #include "core/config/project_settings.h" #include "core/core_string_names.h" -#include "core/input/input.h" #include "core/io/resource_loader.h" #include "core/math/math_defs.h" #include "core/os/keyboard.h" -#include "editor/editor_log.h" +#include "editor/editor_node.h" #include "editor/editor_properties.h" #include "editor/editor_scale.h" -#include "scene/animation/animation_player.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/filesystem_dock.h" +#include "editor/inspector_dock.h" +#include "editor/plugins/curve_editor_plugin.h" +#include "editor/plugins/shader_editor_plugin.h" +#include "scene/gui/button.h" +#include "scene/gui/check_box.h" +#include "scene/gui/code_edit.h" +#include "scene/gui/graph_edit.h" #include "scene/gui/menu_button.h" -#include "scene/gui/panel.h" +#include "scene/gui/option_button.h" +#include "scene/gui/popup.h" +#include "scene/gui/rich_text_label.h" +#include "scene/gui/separator.h" +#include "scene/gui/tree.h" +#include "scene/gui/view_panner.h" #include "scene/main/window.h" #include "scene/resources/visual_shader_nodes.h" #include "scene/resources/visual_shader_particle_nodes.h" -#include "scene/resources/visual_shader_sdf_nodes.h" #include "servers/display_server.h" #include "servers/rendering/shader_types.h" @@ -70,12 +82,14 @@ const int MAX_FLOAT_CONST_DEFS = sizeof(float_constant_defs) / sizeof(FloatConst /////////////////// +void VisualShaderNodePlugin::set_editor(VisualShaderEditor *p_editor) { + vseditor = p_editor; +} + Control *VisualShaderNodePlugin::create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node) { - Object *ret; - if (GDVIRTUAL_CALL(_create_editor, p_parent_resource, p_node, ret)) { - return Object::cast_to<Control>(ret); - } - return nullptr; + Object *ret = nullptr; + GDVIRTUAL_CALL(_create_editor, p_parent_resource, p_node, ret); + return Object::cast_to<Control>(ret); } void VisualShaderNodePlugin::_bind_methods() { @@ -84,17 +98,6 @@ void VisualShaderNodePlugin::_bind_methods() { /////////////////// -static Ref<StyleBoxEmpty> make_empty_stylebox(float p_margin_left = -1, float p_margin_top = -1, float p_margin_right = -1, float p_margin_bottom = -1) { - Ref<StyleBoxEmpty> style(memnew(StyleBoxEmpty)); - style->set_default_margin(SIDE_LEFT, p_margin_left * EDSCALE); - style->set_default_margin(SIDE_RIGHT, p_margin_right * EDSCALE); - style->set_default_margin(SIDE_BOTTOM, p_margin_bottom * EDSCALE); - style->set_default_margin(SIDE_TOP, p_margin_top * EDSCALE); - return style; -} - -/////////////////// - VisualShaderGraphPlugin::VisualShaderGraphPlugin() { } @@ -107,57 +110,64 @@ void VisualShaderGraphPlugin::_bind_methods() { ClassDB::bind_method("update_node", &VisualShaderGraphPlugin::update_node); ClassDB::bind_method("update_node_deferred", &VisualShaderGraphPlugin::update_node_deferred); ClassDB::bind_method("set_input_port_default_value", &VisualShaderGraphPlugin::set_input_port_default_value); - ClassDB::bind_method("set_uniform_name", &VisualShaderGraphPlugin::set_uniform_name); + ClassDB::bind_method("set_parameter_name", &VisualShaderGraphPlugin::set_parameter_name); ClassDB::bind_method("set_expression", &VisualShaderGraphPlugin::set_expression); ClassDB::bind_method("update_curve", &VisualShaderGraphPlugin::update_curve); ClassDB::bind_method("update_curve_xyz", &VisualShaderGraphPlugin::update_curve_xyz); } +void VisualShaderGraphPlugin::set_editor(VisualShaderEditor *p_editor) { + editor = p_editor; +} + void VisualShaderGraphPlugin::register_shader(VisualShader *p_shader) { visual_shader = Ref<VisualShader>(p_shader); } -void VisualShaderGraphPlugin::set_connections(List<VisualShader::Connection> &p_connections) { +void VisualShaderGraphPlugin::set_connections(const List<VisualShader::Connection> &p_connections) { connections = p_connections; } -void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p_node_id, int p_port_id) { +void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p_node_id, int p_port_id, bool p_is_valid) { if (visual_shader->get_shader_type() == p_type && links.has(p_node_id) && links[p_node_id].output_ports.has(p_port_id)) { - for (const KeyValue<int, Port> &E : links[p_node_id].output_ports) { + Link &link = links[p_node_id]; + + for (const KeyValue<int, Port> &E : link.output_ports) { if (E.value.preview_button != nullptr) { E.value.preview_button->set_pressed(false); } } + bool is_dirty = link.preview_pos < 0; - if (links[p_node_id].preview_visible && !is_dirty() && links[p_node_id].preview_box != nullptr) { - links[p_node_id].graph_node->remove_child(links[p_node_id].preview_box); - memdelete(links[p_node_id].preview_box); - links[p_node_id].graph_node->reset_size(); - links[p_node_id].preview_visible = false; + if (!is_dirty && link.preview_visible && link.preview_box != nullptr) { + link.graph_node->remove_child(link.preview_box); + memdelete(link.preview_box); + link.preview_box = nullptr; + link.graph_node->reset_size(); + link.preview_visible = false; } - if (p_port_id != -1 && links[p_node_id].output_ports[p_port_id].preview_button != nullptr) { - if (is_dirty()) { - links[p_node_id].preview_pos = links[p_node_id].graph_node->get_child_count(); + if (p_port_id != -1 && link.output_ports[p_port_id].preview_button != nullptr) { + if (is_dirty) { + link.preview_pos = link.graph_node->get_child_count(); } VBoxContainer *vbox = memnew(VBoxContainer); - links[p_node_id].graph_node->add_child(vbox); - if (links[p_node_id].preview_pos != -1) { - links[p_node_id].graph_node->move_child(vbox, links[p_node_id].preview_pos); - } + link.graph_node->add_child(vbox); + link.graph_node->move_child(vbox, link.preview_pos); + link.graph_node->set_slot_draw_stylebox(vbox->get_index(), false); Control *offset = memnew(Control); offset->set_custom_minimum_size(Size2(0, 5 * EDSCALE)); vbox->add_child(offset); VisualShaderNodePortPreview *port_preview = memnew(VisualShaderNodePortPreview); - port_preview->setup(visual_shader, visual_shader->get_shader_type(), p_node_id, p_port_id); + port_preview->setup(visual_shader, visual_shader->get_shader_type(), p_node_id, p_port_id, p_is_valid); port_preview->set_h_size_flags(Control::SIZE_SHRINK_CENTER); vbox->add_child(port_preview); - links[p_node_id].preview_visible = true; - links[p_node_id].preview_box = vbox; - links[p_node_id].output_ports[p_port_id].preview_button->set_pressed(true); + link.preview_visible = true; + link.preview_box = vbox; + link.output_ports[p_port_id].preview_button->set_pressed(true); } } } @@ -170,8 +180,8 @@ void VisualShaderGraphPlugin::update_node(VisualShader::Type p_type, int p_node_ if (p_type != visual_shader->get_shader_type() || !links.has(p_node_id)) { return; } - remove_node(p_type, p_node_id); - add_node(p_type, p_node_id); + remove_node(p_type, p_node_id, true); + add_node(p_type, p_node_id, true); } void VisualShaderGraphPlugin::set_input_port_default_value(VisualShader::Type p_type, int p_node_id, int p_port_id, Variant p_value) { @@ -184,8 +194,10 @@ void VisualShaderGraphPlugin::set_input_port_default_value(VisualShader::Type p_ switch (p_value.get_type()) { case Variant::COLOR: { button->set_custom_minimum_size(Size2(30, 0) * EDSCALE); - if (!button->is_connected("draw", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_draw_color_over_button))) { - button->connect("draw", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_draw_color_over_button), varray(button, p_value)); + + Callable ce = callable_mp(editor, &VisualShaderEditor::_draw_color_over_button); + if (!button->is_connected("draw", ce)) { + button->connect("draw", ce.bind(button, p_value)); } } break; case Variant::BOOL: { @@ -195,18 +207,26 @@ void VisualShaderGraphPlugin::set_input_port_default_value(VisualShader::Type p_ case Variant::FLOAT: { button->set_text(String::num(p_value, 4)); } break; + case Variant::VECTOR2: { + Vector2 v = p_value; + button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3)); + } break; case Variant::VECTOR3: { Vector3 v = p_value; button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3) + "," + String::num(v.z, 3)); } break; + case Variant::QUATERNION: { + Quaternion v = p_value; + button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3) + "," + String::num(v.z, 3) + "," + String::num(v.w, 3)); + } break; default: { } } } -void VisualShaderGraphPlugin::set_uniform_name(VisualShader::Type p_type, int p_node_id, const String &p_name) { - if (visual_shader->get_shader_type() == p_type && links.has(p_node_id) && links[p_node_id].uniform_name != nullptr) { - links[p_node_id].uniform_name->set_text(p_name); +void VisualShaderGraphPlugin::set_parameter_name(VisualShader::Type p_type, int p_node_id, const String &p_name) { + if (visual_shader->get_shader_type() == p_type && links.has(p_node_id) && links[p_node_id].parameter_name != nullptr) { + links[p_node_id].parameter_name->set_text(p_name); } } @@ -252,6 +272,19 @@ void VisualShaderGraphPlugin::set_expression(VisualShader::Type p_type, int p_no links[p_node_id].expression_edit->set_text(p_expression); } +Ref<Script> VisualShaderGraphPlugin::get_node_script(int p_node_id) const { + if (!links.has(p_node_id)) { + return Ref<Script>(); + } + + Ref<VisualShaderNodeCustom> custom = Ref<VisualShaderNodeCustom>(links[p_node_id].visual_node); + if (custom.is_valid()) { + return custom->get_script(); + } + + return Ref<Script>(); +} + void VisualShaderGraphPlugin::update_node_size(int p_node_id) { if (!links.has(p_node_id)) { return; @@ -271,12 +304,12 @@ void VisualShaderGraphPlugin::register_curve_editor(int p_node_id, int p_index, links[p_node_id].curve_editors[p_index] = p_curve_editor; } -void VisualShaderGraphPlugin::update_uniform_refs() { +void VisualShaderGraphPlugin::update_parameter_refs() { for (KeyValue<int, Link> &E : links) { - VisualShaderNodeUniformRef *ref = Object::cast_to<VisualShaderNodeUniformRef>(E.value.visual_node); + VisualShaderNodeParameterRef *ref = Object::cast_to<VisualShaderNodeParameterRef>(E.value.visual_node); if (ref) { - remove_node(E.value.type, E.key); - add_node(E.value.type, E.key); + remove_node(E.value.type, E.key, true); + add_node(E.value.type, E.key, true); } } } @@ -299,56 +332,91 @@ void VisualShaderGraphPlugin::clear_links() { links.clear(); } -bool VisualShaderGraphPlugin::is_dirty() const { - return dirty; -} - -void VisualShaderGraphPlugin::make_dirty(bool p_enabled) { - dirty = p_enabled; -} - void VisualShaderGraphPlugin::register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphNode *p_graph_node) { - links.insert(p_id, { p_type, p_visual_node, p_graph_node, p_visual_node->get_output_port_for_preview() != -1, -1, Map<int, InputPort>(), Map<int, Port>(), nullptr, nullptr, nullptr, { nullptr, nullptr, nullptr } }); + links.insert(p_id, { p_type, p_visual_node, p_graph_node, p_visual_node->get_output_port_for_preview() != -1, -1, HashMap<int, InputPort>(), HashMap<int, Port>(), nullptr, nullptr, nullptr, { nullptr, nullptr, nullptr } }); } void VisualShaderGraphPlugin::register_output_port(int p_node_id, int p_port, TextureButton *p_button) { links[p_node_id].output_ports.insert(p_port, { p_button }); } -void VisualShaderGraphPlugin::register_uniform_name(int p_node_id, LineEdit *p_uniform_name) { - links[p_node_id].uniform_name = p_uniform_name; +void VisualShaderGraphPlugin::register_parameter_name(int p_node_id, LineEdit *p_parameter_name) { + links[p_node_id].parameter_name = p_parameter_name; } void VisualShaderGraphPlugin::update_theme() { - vector_expanded_color[0] = VisualShaderEditor::get_singleton()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor")); // red - vector_expanded_color[1] = VisualShaderEditor::get_singleton()->get_theme_color(SNAME("axis_y_color"), SNAME("Editor")); // green - vector_expanded_color[2] = VisualShaderEditor::get_singleton()->get_theme_color(SNAME("axis_z_color"), SNAME("Editor")); // blue + vector_expanded_color[0] = editor->get_theme_color(SNAME("axis_x_color"), SNAME("Editor")); // red + vector_expanded_color[1] = editor->get_theme_color(SNAME("axis_y_color"), SNAME("Editor")); // green + vector_expanded_color[2] = editor->get_theme_color(SNAME("axis_z_color"), SNAME("Editor")); // blue + vector_expanded_color[3] = editor->get_theme_color(SNAME("axis_w_color"), SNAME("Editor")); // alpha +} + +bool VisualShaderGraphPlugin::is_node_has_parameter_instances_relatively(VisualShader::Type p_type, int p_node) const { + bool result = false; + + Ref<VisualShaderNodeParameter> parameter_node = Object::cast_to<VisualShaderNodeParameter>(visual_shader->get_node_unchecked(p_type, p_node).ptr()); + if (parameter_node.is_valid()) { + if (parameter_node->get_qualifier() == VisualShaderNodeParameter::QUAL_INSTANCE) { + return true; + } + } + + LocalVector<int> prev_connected_nodes; + visual_shader->get_prev_connected_nodes(p_type, p_node, prev_connected_nodes); + + for (const int &E : prev_connected_nodes) { + result = is_node_has_parameter_instances_relatively(p_type, E); + if (result) { + break; + } + } + + return result; } -void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { - if (p_type != visual_shader->get_shader_type()) { +void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool p_just_update) { + if (!visual_shader.is_valid() || p_type != visual_shader->get_shader_type()) { + return; + } + GraphEdit *graph = editor->graph; + if (!graph) { return; } + VisualShaderGraphPlugin *graph_plugin = editor->get_graph_plugin(); + if (!graph_plugin) { + return; + } + Shader::Mode mode = visual_shader->get_mode(); Control *offset; - static Ref<StyleBoxEmpty> label_style = make_empty_stylebox(2, 1, 2, 1); - - static const Color type_color[6] = { + static const Color type_color[] = { Color(0.38, 0.85, 0.96), // scalar (float) Color(0.49, 0.78, 0.94), // scalar (int) - Color(0.84, 0.49, 0.93), // vector + Color(0.20, 0.88, 0.67), // scalar (uint) + Color(0.74, 0.57, 0.95), // vector2 + Color(0.84, 0.49, 0.93), // vector3 + Color(1.0, 0.125, 0.95), // vector4 Color(0.55, 0.65, 0.94), // boolean Color(0.96, 0.66, 0.43), // transform Color(1.0, 1.0, 0.0), // sampler }; - static const String vector_expanded_name[3] = { + static const String vector_expanded_name[4] = { "red", "green", - "blue" + "blue", + "alpha" }; + // Visual shader specific theme for MSDF font. + Ref<Theme> vstheme; + vstheme.instantiate(); + Ref<Font> label_font = EditorNode::get_singleton()->get_editor_theme()->get_font("main_msdf", "EditorFonts"); + vstheme->set_font("font", "Label", label_font); + vstheme->set_font("font", "LineEdit", label_font); + vstheme->set_font("font", "Button", label_font); + Ref<VisualShaderNode> vsnode = visual_shader->get_node(p_type, p_id); Ref<VisualShaderNodeResizableBase> resizable_node = Object::cast_to<VisualShaderNodeResizableBase>(vsnode.ptr()); @@ -369,14 +437,29 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { custom_node->_set_initialized(true); } + // Create graph node. GraphNode *node = memnew(GraphNode); - register_link(p_type, p_id, vsnode.ptr(), node); + graph->add_child(node); + node->set_theme(vstheme); + editor->_update_created_node(node); + + if (p_just_update) { + Link &link = links[p_id]; + + link.graph_node = node; + link.preview_box = nullptr; + link.preview_pos = -1; + link.output_ports.clear(); + link.input_ports.clear(); + } else { + register_link(p_type, p_id, vsnode.ptr(), node); + } if (is_resizable) { size = resizable_node->get_size(); node->set_resizable(true); - node->connect("resize_request", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_node_resized), varray((int)p_type, p_id)); + node->connect("resize_request", callable_mp(editor, &VisualShaderEditor::_node_resized).bind((int)p_type, p_id)); } if (is_expression) { expression = expression_node->get_expression(); @@ -388,10 +471,10 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (p_id >= 2) { node->set_show_close_button(true); - node->connect("close_request", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_delete_node_request), varray(p_type, p_id), CONNECT_DEFERRED); + node->connect("close_request", callable_mp(editor, &VisualShaderEditor::_delete_node_request).bind(p_type, p_id), CONNECT_DEFERRED); } - node->connect("dragged", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_node_dragged), varray(p_id)); + node->connect("dragged", callable_mp(editor, &VisualShaderEditor::_node_dragged).bind(p_id)); Control *custom_editor = nullptr; int port_offset = 1; @@ -416,6 +499,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { comment_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); comment_label->set_text(comment_node->get_description()); } + editor->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); } Ref<VisualShaderNodeParticleEmit> emit = vsnode; @@ -423,33 +507,37 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { node->set_custom_minimum_size(Size2(200 * EDSCALE, 0)); } - Ref<VisualShaderNodeUniform> uniform = vsnode; - if (uniform.is_valid()) { - VisualShaderEditor::get_singleton()->graph->add_child(node); - VisualShaderEditor::get_singleton()->_update_created_node(node); + Ref<VisualShaderNodeParameterRef> parameter_ref = vsnode; + if (parameter_ref.is_valid()) { + parameter_ref->set_shader_rid(visual_shader->get_rid()); + parameter_ref->update_parameter_type(); + } - LineEdit *uniform_name = memnew(LineEdit); - register_uniform_name(p_id, uniform_name); - uniform_name->set_text(uniform->get_uniform_name()); - node->add_child(uniform_name); - uniform_name->connect("text_submitted", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_uniform_line_edit_changed), varray(p_id)); - uniform_name->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_uniform_line_edit_focus_out), varray(uniform_name, p_id)); + Ref<VisualShaderNodeParameter> parameter = vsnode; + HBoxContainer *hb = nullptr; - if (vsnode->get_input_port_count() == 0 && vsnode->get_output_port_count() == 1 && vsnode->get_output_port_name(0) == "") { - //shortcut - VisualShaderNode::PortType port_right = vsnode->get_output_port_type(0); - node->set_slot(1, false, VisualShaderNode::PORT_TYPE_SCALAR, Color(), true, port_right, type_color[port_right]); - if (!vsnode->is_use_prop_slots()) { - return; - } + if (parameter.is_valid()) { + LineEdit *parameter_name = memnew(LineEdit); + register_parameter_name(p_id, parameter_name); + parameter_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); + parameter_name->set_text(parameter->get_parameter_name()); + parameter_name->connect("text_submitted", callable_mp(editor, &VisualShaderEditor::_parameter_line_edit_changed).bind(p_id)); + parameter_name->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_parameter_line_edit_focus_out).bind(parameter_name, p_id)); + + if (vsnode->get_output_port_count() == 1 && vsnode->get_output_port_name(0) == "") { + hb = memnew(HBoxContainer); + hb->add_child(parameter_name); + node->add_child(hb); + } else { + node->add_child(parameter_name); } port_offset++; } - for (int i = 0; i < VisualShaderEditor::get_singleton()->plugins.size(); i++) { + for (int i = 0; i < editor->plugins.size(); i++) { vsnode->set_meta("id", p_id); vsnode->set_meta("shader_type", (int)p_type); - custom_editor = VisualShaderEditor::get_singleton()->plugins.write[i]->create_editor(visual_shader, vsnode); + custom_editor = editor->plugins.write[i]->create_editor(visual_shader, vsnode); vsnode->remove_meta("id"); vsnode->remove_meta("shader_type"); if (custom_editor) { @@ -461,146 +549,76 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { } Ref<VisualShaderNodeCurveTexture> curve = vsnode; - if (curve.is_valid()) { - if (curve->get_texture().is_valid() && !curve->get_texture()->is_connected("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve))) { - curve->get_texture()->connect("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve), varray(p_id)); - } - - HBoxContainer *hbox = memnew(HBoxContainer); - custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - hbox->add_child(custom_editor); - custom_editor = hbox; - } - Ref<VisualShaderNodeCurveXYZTexture> curve_xyz = vsnode; - if (curve_xyz.is_valid()) { - if (curve_xyz->get_texture().is_valid() && !curve_xyz->get_texture()->is_connected("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve_xyz))) { - curve_xyz->get_texture()->connect("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve_xyz), varray(p_id)); - } - HBoxContainer *hbox = memnew(HBoxContainer); - custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - hbox->add_child(custom_editor); - custom_editor = hbox; + bool is_curve = curve.is_valid() || curve_xyz.is_valid(); + if (is_curve) { + hb = memnew(HBoxContainer); + node->add_child(hb); } - if (custom_editor && !vsnode->is_use_prop_slots() && vsnode->get_output_port_count() > 0 && vsnode->get_output_port_name(0) == "" && (vsnode->get_input_port_count() == 0 || vsnode->get_input_port_name(0) == "")) { - //will be embedded in first port - } else if (custom_editor) { - port_offset++; - node->add_child(custom_editor); - - bool is_curve = curve.is_valid() || curve_xyz.is_valid(); - - if (is_curve) { - // a default value handling - { - Variant default_value; - bool port_left_used = false; - - for (const VisualShader::Connection &E : connections) { - if (E.to_node == p_id && E.to_port == 0) { - port_left_used = true; - break; - } - } - - if (!port_left_used) { - default_value = vsnode->get_input_port_default_value(0); - } - - Button *button = memnew(Button); - custom_editor->add_child(button); - register_default_input_button(p_id, 0, button); - custom_editor->move_child(button, 0); - - button->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_edit_port_default_input), varray(button, p_id, 0)); - if (default_value.get_type() != Variant::NIL) { - set_input_port_default_value(p_type, p_id, 0, default_value); - } else { - button->hide(); - } - } - - VisualShaderEditor::get_singleton()->graph->add_child(node); - VisualShaderEditor::get_singleton()->_update_created_node(node); - - TextureButton *preview = memnew(TextureButton); - preview->set_toggle_mode(true); - preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"))); - preview->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("EditorIcons"))); - preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER); - - register_output_port(p_id, 0, preview); - - preview->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_preview_select_port), varray(p_id, 0), CONNECT_DEFERRED); - custom_editor->add_child(preview); + if (curve.is_valid()) { + custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - if (vsnode->get_output_port_for_preview() >= 0) { - show_port_preview(p_type, p_id, vsnode->get_output_port_for_preview()); - } + Callable ce = callable_mp(graph_plugin, &VisualShaderGraphPlugin::update_curve); + if (curve->get_texture().is_valid() && !curve->get_texture()->is_connected("changed", ce)) { + curve->get_texture()->connect("changed", ce.bind(p_id)); } - if (curve.is_valid()) { - CurveEditor *curve_editor = memnew(CurveEditor); - node->add_child(curve_editor); - register_curve_editor(p_id, 0, curve_editor); - curve_editor->set_custom_minimum_size(Size2(300, 0)); - curve_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - if (curve->get_texture().is_valid()) { - curve_editor->set_curve(curve->get_texture()->get_curve()); - } + CurveEditor *curve_editor = memnew(CurveEditor); + node->add_child(curve_editor); + register_curve_editor(p_id, 0, curve_editor); + curve_editor->set_custom_minimum_size(Size2(300, 0)); + curve_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (curve->get_texture().is_valid()) { + curve_editor->set_curve(curve->get_texture()->get_curve()); } + } - if (curve_xyz.is_valid()) { - CurveEditor *curve_editor_x = memnew(CurveEditor); - node->add_child(curve_editor_x); - register_curve_editor(p_id, 0, curve_editor_x); - curve_editor_x->set_custom_minimum_size(Size2(300, 0)); - curve_editor_x->set_h_size_flags(Control::SIZE_EXPAND_FILL); - if (curve_xyz->get_texture().is_valid()) { - curve_editor_x->set_curve(curve_xyz->get_texture()->get_curve_x()); - } - - CurveEditor *curve_editor_y = memnew(CurveEditor); - node->add_child(curve_editor_y); - register_curve_editor(p_id, 1, curve_editor_y); - curve_editor_y->set_custom_minimum_size(Size2(300, 0)); - curve_editor_y->set_h_size_flags(Control::SIZE_EXPAND_FILL); - if (curve_xyz->get_texture().is_valid()) { - curve_editor_y->set_curve(curve_xyz->get_texture()->get_curve_y()); - } + if (curve_xyz.is_valid()) { + custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - CurveEditor *curve_editor_z = memnew(CurveEditor); - node->add_child(curve_editor_z); - register_curve_editor(p_id, 2, curve_editor_z); - curve_editor_z->set_custom_minimum_size(Size2(300, 0)); - curve_editor_z->set_h_size_flags(Control::SIZE_EXPAND_FILL); - if (curve_xyz->get_texture().is_valid()) { - curve_editor_z->set_curve(curve_xyz->get_texture()->get_curve_z()); - } + Callable ce = callable_mp(graph_plugin, &VisualShaderGraphPlugin::update_curve_xyz); + if (curve_xyz->get_texture().is_valid() && !curve_xyz->get_texture()->is_connected("changed", ce)) { + curve_xyz->get_texture()->connect("changed", ce.bind(p_id)); } - if (is_curve) { - VisualShaderNode::PortType port_left = vsnode->get_input_port_type(0); - VisualShaderNode::PortType port_right = vsnode->get_output_port_type(0); - node->set_slot(1, true, port_left, type_color[port_left], true, port_right, type_color[port_right]); + CurveEditor *curve_editor_x = memnew(CurveEditor); + node->add_child(curve_editor_x); + register_curve_editor(p_id, 0, curve_editor_x); + curve_editor_x->set_custom_minimum_size(Size2(300, 0)); + curve_editor_x->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (curve_xyz->get_texture().is_valid()) { + curve_editor_x->set_curve(curve_xyz->get_texture()->get_curve_x()); + } - VisualShaderEditor::get_singleton()->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); + CurveEditor *curve_editor_y = memnew(CurveEditor); + node->add_child(curve_editor_y); + register_curve_editor(p_id, 1, curve_editor_y); + curve_editor_y->set_custom_minimum_size(Size2(300, 0)); + curve_editor_y->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (curve_xyz->get_texture().is_valid()) { + curve_editor_y->set_curve(curve_xyz->get_texture()->get_curve_y()); } - if (vsnode->is_use_prop_slots()) { - String error = vsnode->get_warning(visual_shader->get_mode(), p_type); - if (!error.is_empty()) { - Label *error_label = memnew(Label); - error_label->add_theme_color_override("font_color", VisualShaderEditor::get_singleton()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); - error_label->set_text(error); - node->add_child(error_label); - } + CurveEditor *curve_editor_z = memnew(CurveEditor); + node->add_child(curve_editor_z); + register_curve_editor(p_id, 2, curve_editor_z); + curve_editor_z->set_custom_minimum_size(Size2(300, 0)); + curve_editor_z->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (curve_xyz->get_texture().is_valid()) { + curve_editor_z->set_curve(curve_xyz->get_texture()->get_curve_z()); + } + } - return; + if (custom_editor) { + if (is_curve || (hb == nullptr && !vsnode->is_use_prop_slots() && (vsnode->get_output_port_count() == 0 || vsnode->get_output_port_name(0) == "") && (vsnode->get_input_port_count() == 0 || vsnode->get_input_port_name(0) == ""))) { + //will be embedded in first port + } else { + port_offset++; + node->add_child(custom_editor); + custom_editor = nullptr; } - custom_editor = nullptr; } if (is_group) { @@ -625,14 +643,14 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { Button *add_input_btn = memnew(Button); add_input_btn->set_text(TTR("Add Input")); - add_input_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_add_input_port), varray(p_id, group_node->get_free_input_port_id(), VisualShaderNode::PORT_TYPE_VECTOR, input_port_name), CONNECT_DEFERRED); + add_input_btn->connect("pressed", callable_mp(editor, &VisualShaderEditor::_add_input_port).bind(p_id, group_node->get_free_input_port_id(), VisualShaderNode::PORT_TYPE_VECTOR_3D, input_port_name), CONNECT_DEFERRED); hb2->add_child(add_input_btn); hb2->add_spacer(); Button *add_output_btn = memnew(Button); add_output_btn->set_text(TTR("Add Output")); - add_output_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_add_output_port), varray(p_id, group_node->get_free_output_port_id(), VisualShaderNode::PORT_TYPE_VECTOR, output_port_name), CONNECT_DEFERRED); + add_output_btn->connect("pressed", callable_mp(editor, &VisualShaderEditor::_add_output_port).bind(p_id, group_node->get_free_output_port_id(), VisualShaderNode::PORT_TYPE_VECTOR_3D, output_port_name), CONNECT_DEFERRED); hb2->add_child(add_output_btn); node->add_child(hb2); @@ -642,8 +660,18 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { int output_port_count = 0; for (int i = 0; i < vsnode->get_output_port_count(); i++) { if (vsnode->_is_output_port_expanded(i)) { - if (vsnode->get_output_port_type(i) == VisualShaderNode::PORT_TYPE_VECTOR) { - output_port_count += 3; + switch (vsnode->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + output_port_count += 2; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + output_port_count += 3; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + output_port_count += 4; + } break; + default: + break; } } output_port_count++; @@ -653,10 +681,30 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { int expanded_port_counter = 0; for (int i = 0, j = 0; i < max_ports; i++, j++) { - if (expanded_type == VisualShaderNode::PORT_TYPE_VECTOR && expanded_port_counter >= 3) { - expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; - expanded_port_counter = 0; - i -= 3; + switch (expanded_type) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + if (expanded_port_counter >= 2) { + expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; + expanded_port_counter = 0; + i -= 2; + } + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + if (expanded_port_counter >= 3) { + expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; + expanded_port_counter = 0; + i -= 3; + } + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + if (expanded_port_counter >= 4) { + expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; + expanded_port_counter = 0; + i -= 4; + } + } break; + default: + break; } if (vsnode->is_port_separator(i)) { @@ -693,7 +741,12 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { name_right = vector_expanded_name[expanded_port_counter++]; } - HBoxContainer *hb = memnew(HBoxContainer); + bool is_first_hbox = false; + if (i == 0 && hb != nullptr) { + is_first_hbox = true; + } else { + hb = memnew(HBoxContainer); + } hb->add_theme_constant_override("separation", 7 * EDSCALE); Variant default_value; @@ -705,7 +758,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { Button *button = memnew(Button); hb->add_child(button); register_default_input_button(p_id, i, button); - button->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_edit_port_default_input), varray(button, p_id, i)); + button->connect("pressed", callable_mp(editor, &VisualShaderEditor::_edit_port_default_input).bind(button, p_id, i)); if (default_value.get_type() != Variant::NIL) { // only a label set_input_port_default_value(p_type, p_id, i, default_value); } else { @@ -722,44 +775,47 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { hb->add_child(type_box); type_box->add_item(TTR("Float")); type_box->add_item(TTR("Int")); - type_box->add_item(TTR("Vector")); + type_box->add_item(TTR("UInt")); + type_box->add_item(TTR("Vector2")); + type_box->add_item(TTR("Vector3")); + type_box->add_item(TTR("Vector4")); type_box->add_item(TTR("Boolean")); type_box->add_item(TTR("Transform")); type_box->add_item(TTR("Sampler")); type_box->select(group_node->get_input_port_type(i)); type_box->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); - type_box->connect("item_selected", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_input_port_type), varray(p_id, i), CONNECT_DEFERRED); + type_box->connect("item_selected", callable_mp(editor, &VisualShaderEditor::_change_input_port_type).bind(p_id, i), CONNECT_DEFERRED); LineEdit *name_box = memnew(LineEdit); hb->add_child(name_box); name_box->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); name_box->set_text(name_left); - name_box->connect("text_submitted", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_input_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED); - name_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_port_name_focus_out), varray(name_box, p_id, i, false), CONNECT_DEFERRED); + name_box->connect("text_submitted", callable_mp(editor, &VisualShaderEditor::_change_input_port_name).bind(name_box, p_id, i), CONNECT_DEFERRED); + name_box->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_port_name_focus_out).bind(name_box, p_id, i, false), CONNECT_DEFERRED); Button *remove_btn = memnew(Button); remove_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - remove_btn->set_tooltip(TTR("Remove") + " " + name_left); - remove_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_remove_input_port), varray(p_id, i), CONNECT_DEFERRED); + remove_btn->set_tooltip_text(TTR("Remove") + " " + name_left); + remove_btn->connect("pressed", callable_mp(editor, &VisualShaderEditor::_remove_input_port).bind(p_id, i), CONNECT_DEFERRED); hb->add_child(remove_btn); } else { Label *label = memnew(Label); label->set_text(name_left); - label->add_theme_style_override("normal", label_style); //more compact + label->add_theme_style_override("normal", editor->get_theme_stylebox(SNAME("label_style"), SNAME("VShaderEditor"))); //more compact hb->add_child(label); - if (vsnode->get_input_port_default_hint(i) != "" && !port_left_used) { + if (vsnode->is_input_port_default(i, mode) && !port_left_used) { Label *hint_label = memnew(Label); - hint_label->set_text("[" + vsnode->get_input_port_default_hint(i) + "]"); - hint_label->add_theme_color_override("font_color", VisualShaderEditor::get_singleton()->get_theme_color(SNAME("font_readonly_color"), SNAME("TextEdit"))); - hint_label->add_theme_style_override("normal", label_style); + hint_label->set_text(TTR("[default]")); + hint_label->add_theme_color_override("font_color", editor->get_theme_color(SNAME("font_readonly_color"), SNAME("TextEdit"))); + hint_label->add_theme_style_override("normal", editor->get_theme_stylebox(SNAME("label_style"), SNAME("VShaderEditor"))); hb->add_child(hint_label); } } } - if (!is_group) { + if (!is_group && !is_first_hbox) { hb->add_spacer(); } @@ -767,8 +823,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (is_group) { Button *remove_btn = memnew(Button); remove_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - remove_btn->set_tooltip(TTR("Remove") + " " + name_left); - remove_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_remove_output_port), varray(p_id, i), CONNECT_DEFERRED); + remove_btn->set_tooltip_text(TTR("Remove") + " " + name_left); + remove_btn->connect("pressed", callable_mp(editor, &VisualShaderEditor::_remove_output_port).bind(p_id, i), CONNECT_DEFERRED); hb->add_child(remove_btn); LineEdit *name_box = memnew(LineEdit); @@ -776,23 +832,26 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { name_box->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); name_box->set_text(name_right); - name_box->connect("text_submitted", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_output_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED); - name_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_port_name_focus_out), varray(name_box, p_id, i, true), CONNECT_DEFERRED); + name_box->connect("text_submitted", callable_mp(editor, &VisualShaderEditor::_change_output_port_name).bind(name_box, p_id, i), CONNECT_DEFERRED); + name_box->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_port_name_focus_out).bind(name_box, p_id, i, true), CONNECT_DEFERRED); OptionButton *type_box = memnew(OptionButton); hb->add_child(type_box); type_box->add_item(TTR("Float")); type_box->add_item(TTR("Int")); - type_box->add_item(TTR("Vector")); + type_box->add_item(TTR("UInt")); + type_box->add_item(TTR("Vector2")); + type_box->add_item(TTR("Vector3")); + type_box->add_item(TTR("Vector4")); type_box->add_item(TTR("Boolean")); type_box->add_item(TTR("Transform")); type_box->select(group_node->get_output_port_type(i)); type_box->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); - type_box->connect("item_selected", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_output_port_type), varray(p_id, i), CONNECT_DEFERRED); + type_box->connect("item_selected", callable_mp(editor, &VisualShaderEditor::_change_output_port_type).bind(p_id, i), CONNECT_DEFERRED); } else { Label *label = memnew(Label); label->set_text(name_right); - label->add_theme_style_override("normal", label_style); //more compact + label->add_theme_style_override("normal", editor->get_theme_stylebox(SNAME("label_style"), SNAME("VShaderEditor"))); //more compact hb->add_child(label); } } @@ -802,23 +861,23 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (vsnode->is_output_port_expandable(i)) { TextureButton *expand = memnew(TextureButton); expand->set_toggle_mode(true); - expand->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiTreeArrowDown"), SNAME("EditorIcons"))); - expand->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiTreeArrowRight"), SNAME("EditorIcons"))); + expand->set_texture_normal(editor->get_theme_icon(SNAME("GuiTreeArrowDown"), SNAME("EditorIcons"))); + expand->set_texture_pressed(editor->get_theme_icon(SNAME("GuiTreeArrowRight"), SNAME("EditorIcons"))); expand->set_v_size_flags(Control::SIZE_SHRINK_CENTER); expand->set_pressed(vsnode->_is_output_port_expanded(i)); - expand->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_expand_output_port), varray(p_id, i, !vsnode->_is_output_port_expanded(i)), CONNECT_DEFERRED); + expand->connect("pressed", callable_mp(editor, &VisualShaderEditor::_expand_output_port).bind(p_id, i, !vsnode->_is_output_port_expanded(i)), CONNECT_DEFERRED); hb->add_child(expand); } if (vsnode->has_output_port_preview(i) && port_right != VisualShaderNode::PORT_TYPE_TRANSFORM && port_right != VisualShaderNode::PORT_TYPE_SAMPLER) { TextureButton *preview = memnew(TextureButton); preview->set_toggle_mode(true); - preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"))); - preview->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("EditorIcons"))); + preview->set_texture_normal(editor->get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"))); + preview->set_texture_pressed(editor->get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("EditorIcons"))); preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER); register_output_port(p_id, j, preview); - preview->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_preview_select_port), varray(p_id, j), CONNECT_DEFERRED); + preview->connect("pressed", callable_mp(editor, &VisualShaderEditor::_preview_select_port).bind(p_id, j), CONNECT_DEFERRED); hb->add_child(preview); } } @@ -830,56 +889,126 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { port_offset++; } - node->add_child(hb); + if (!is_first_hbox) { + node->add_child(hb); + } if (expanded_type != VisualShaderNode::PORT_TYPE_SCALAR) { continue; } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]); + int idx = 1; + if (!is_first_hbox) { + idx = i + port_offset; + } + node->set_slot(idx, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]); if (vsnode->_is_output_port_expanded(i)) { - if (vsnode->get_output_port_type(i) == VisualShaderNode::PORT_TYPE_VECTOR) { - port_offset++; - valid_left = (i + 1) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 1); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); - port_offset++; + switch (vsnode->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + port_offset++; + valid_left = (i + 1) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 1); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); + port_offset++; - valid_left = (i + 2) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 2); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); - port_offset++; + valid_left = (i + 2) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 2); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); + + expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_2D; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + port_offset++; + valid_left = (i + 1) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 1); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); + port_offset++; - valid_left = (i + 3) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 3); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); - expanded_type = VisualShaderNode::PORT_TYPE_VECTOR; + valid_left = (i + 2) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 2); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); + port_offset++; + + valid_left = (i + 3) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 3); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); + + expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_3D; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + port_offset++; + valid_left = (i + 1) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 1); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); + port_offset++; + + valid_left = (i + 2) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 2); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); + port_offset++; + + valid_left = (i + 3) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 3); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); + port_offset++; + + valid_left = (i + 4) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 4); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[3]); + + expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_4D; + } break; + default: + break; } } } + bool has_relative_parameter_instances = false; if (vsnode->get_output_port_for_preview() >= 0) { - show_port_preview(p_type, p_id, vsnode->get_output_port_for_preview()); + has_relative_parameter_instances = is_node_has_parameter_instances_relatively(p_type, p_id); + show_port_preview(p_type, p_id, vsnode->get_output_port_for_preview(), !has_relative_parameter_instances); + } else { + offset = memnew(Control); + offset->set_custom_minimum_size(Size2(0, 4 * EDSCALE)); + node->add_child(offset); } - offset = memnew(Control); - offset->set_custom_minimum_size(Size2(0, 4 * EDSCALE)); - node->add_child(offset); - - String error = vsnode->get_warning(visual_shader->get_mode(), p_type); + String error = vsnode->get_warning(mode, p_type); + if (has_relative_parameter_instances) { + error += "\n" + TTR("The 2D preview cannot correctly show the result retrieved from instance parameter."); + } if (!error.is_empty()) { Label *error_label = memnew(Label); - error_label->add_theme_color_override("font_color", VisualShaderEditor::get_singleton()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); + error_label->add_theme_color_override("font_color", editor->get_theme_color(SNAME("error_color"), SNAME("Editor"))); error_label->set_text(error); node->add_child(error_label); } @@ -905,7 +1034,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { expression_box->set_syntax_highlighter(expression_syntax_highlighter); expression_box->add_theme_color_override("background_color", background_color); - for (const String &E : VisualShaderEditor::get_singleton()->keyword_list) { + for (const String &E : editor->keyword_list) { if (ShaderLanguage::is_control_flow_keyword(E)) { expression_syntax_highlighter->add_keyword_color(E, control_flow_keyword_color); } else { @@ -913,8 +1042,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { } } - expression_box->add_theme_font_override("font", VisualShaderEditor::get_singleton()->get_theme_font(SNAME("expression"), SNAME("EditorFonts"))); - expression_box->add_theme_font_size_override("font_size", VisualShaderEditor::get_singleton()->get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts"))); + expression_box->add_theme_font_override("font", editor->get_theme_font(SNAME("expression"), SNAME("EditorFonts"))); + expression_box->add_theme_font_size_override("font_size", editor->get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts"))); expression_box->add_theme_color_override("font_color", text_color); expression_syntax_highlighter->set_number_color(number_color); expression_syntax_highlighter->set_symbol_color(symbol_color); @@ -935,32 +1064,33 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { expression_box->set_context_menu_enabled(false); expression_box->set_draw_line_numbers(true); - expression_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_expression_focus_out), varray(expression_box, p_id)); + expression_box->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_expression_focus_out).bind(expression_box, p_id)); } - if (!uniform.is_valid()) { - VisualShaderEditor::get_singleton()->graph->add_child(node); - if (is_comment) { - VisualShaderEditor::get_singleton()->graph->move_child(node, 0); // to prevents a bug where comment node overlaps its content - } - VisualShaderEditor::get_singleton()->_update_created_node(node); - if (is_resizable) { - VisualShaderEditor::get_singleton()->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); - } + if (is_comment) { + graph->move_child(node, 0); // to prevents a bug where comment node overlaps its content } } -void VisualShaderGraphPlugin::remove_node(VisualShader::Type p_type, int p_id) { +void VisualShaderGraphPlugin::remove_node(VisualShader::Type p_type, int p_id, bool p_just_update) { if (visual_shader->get_shader_type() == p_type && links.has(p_id)) { links[p_id].graph_node->get_parent()->remove_child(links[p_id].graph_node); memdelete(links[p_id].graph_node); - links.erase(p_id); + if (!p_just_update) { + links.erase(p_id); + } } } void VisualShaderGraphPlugin::connect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { - if (visual_shader->get_shader_type() == p_type) { - VisualShaderEditor::get_singleton()->graph->connect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port); + GraphEdit *graph = editor->graph; + if (!graph) { + return; + } + + if (visual_shader.is_valid() && visual_shader->get_shader_type() == p_type) { + graph->connect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port); + connections.push_back({ p_from_node, p_from_port, p_to_node, p_to_port }); if (links[p_to_node].input_ports.has(p_to_port) && links[p_to_node].input_ports[p_to_port].default_input_button != nullptr) { links[p_to_node].input_ports[p_to_port].default_input_button->hide(); @@ -969,8 +1099,14 @@ void VisualShaderGraphPlugin::connect_nodes(VisualShader::Type p_type, int p_fro } void VisualShaderGraphPlugin::disconnect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { - if (visual_shader->get_shader_type() == p_type) { - VisualShaderEditor::get_singleton()->graph->disconnect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port); + GraphEdit *graph = editor->graph; + if (!graph) { + return; + } + + if (visual_shader.is_valid() && visual_shader->get_shader_type() == p_type) { + graph->disconnect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port); + for (const List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) { if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) { connections.erase(E); @@ -989,6 +1125,27 @@ VisualShaderGraphPlugin::~VisualShaderGraphPlugin() { ///////////////// +void VisualShaderEditedProperty::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_edited_property", "value"), &VisualShaderEditedProperty::set_edited_property); + ClassDB::bind_method(D_METHOD("get_edited_property"), &VisualShaderEditedProperty::get_edited_property); + + ADD_PROPERTY(PropertyInfo(Variant::NIL, "edited_property", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "set_edited_property", "get_edited_property"); +} + +void VisualShaderEditedProperty::set_edited_property(Variant p_variant) { + edited_property = p_variant; +} + +Variant VisualShaderEditedProperty::get_edited_property() const { + return edited_property; +} + +///////////////// + +Vector2 VisualShaderEditor::selection_center; +List<VisualShaderEditor::CopyItem> VisualShaderEditor::copy_items_buffer; +List<VisualShader::Connection> VisualShaderEditor::copy_connections_buffer; + void VisualShaderEditor::edit(VisualShader *p_visual_shader) { bool changed = false; if (p_visual_shader) { @@ -1001,36 +1158,20 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) { } visual_shader = Ref<VisualShader>(p_visual_shader); graph_plugin->register_shader(visual_shader.ptr()); - if (!visual_shader->is_connected("changed", callable_mp(this, &VisualShaderEditor::_update_preview))) { - visual_shader->connect("changed", callable_mp(this, &VisualShaderEditor::_update_preview)); - } -#ifndef DISABLE_DEPRECATED - Dictionary engine_version = Engine::get_singleton()->get_version_info(); - static Array components; - if (components.is_empty()) { - components.push_back("major"); - components.push_back("minor"); - } - const Dictionary vs_version = visual_shader->get_engine_version(); - if (!vs_version.has_all(components)) { - visual_shader->update_engine_version(engine_version); - print_line(vformat(TTR("The shader (\"%s\") has been updated to correspond Godot %s.%s version."), visual_shader->get_path(), engine_version["major"], engine_version["minor"])); - } else { - for (int i = 0; i < components.size(); i++) { - if (vs_version[components[i]] != engine_version[components[i]]) { - visual_shader->update_engine_version(engine_version); - print_line(vformat(TTR("The shader (\"%s\") has been updated to correspond Godot %s.%s version."), visual_shader->get_path(), engine_version["major"], engine_version["minor"])); - break; - } - } + + Callable ce = callable_mp(this, &VisualShaderEditor::_update_preview); + if (!visual_shader->is_connected("changed", ce)) { + visual_shader->connect("changed", ce); } -#endif visual_shader->set_graph_offset(graph->get_scroll_ofs() / EDSCALE); _set_mode(visual_shader->get_mode()); + + _update_nodes(); } else { if (visual_shader.is_valid()) { - if (visual_shader->is_connected("changed", callable_mp(this, &VisualShaderEditor::_update_preview))) { - visual_shader->disconnect("changed", callable_mp(this, &VisualShaderEditor::_update_preview)); + Callable ce = callable_mp(this, &VisualShaderEditor::_update_preview); + if (visual_shader->is_connected("changed", ce)) { + visual_shader->disconnect("changed", ce); } } visual_shader.unref(); @@ -1040,6 +1181,7 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) { hide(); } else { if (changed) { // to avoid tree collapse + _update_varying_tree(); _update_options_menu(); _update_preview(); _update_graph(); @@ -1048,7 +1190,7 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) { } void VisualShaderEditor::add_plugin(const Ref<VisualShaderNodePlugin> &p_plugin) { - if (plugins.find(p_plugin) != -1) { + if (plugins.has(p_plugin)) { return; } plugins.push_back(p_plugin); @@ -1108,6 +1250,327 @@ void VisualShaderEditor::add_custom_type(const String &p_name, const Ref<Script> add_options.push_back(ao); } +Dictionary VisualShaderEditor::get_custom_node_data(Ref<VisualShaderNodeCustom> &p_custom_node) { + Dictionary dict; + dict["script"] = p_custom_node->get_script(); + + String name; + if (p_custom_node->has_method("_get_name")) { + name = (String)p_custom_node->call("_get_name"); + } else { + name = "Unnamed"; + } + dict["name"] = name; + + String description = ""; + if (p_custom_node->has_method("_get_description")) { + description = (String)p_custom_node->call("_get_description"); + } + dict["description"] = description; + + int return_icon_type = -1; + if (p_custom_node->has_method("_get_return_icon_type")) { + return_icon_type = (int)p_custom_node->call("_get_return_icon_type"); + } + dict["return_icon_type"] = return_icon_type; + + String category = ""; + if (p_custom_node->has_method("_get_category")) { + category = (String)p_custom_node->call("_get_category"); + } + category = category.rstrip("/"); + category = category.lstrip("/"); + category = "Addons/" + category; + + String subcategory = ""; + if (p_custom_node->has_method("_get_subcategory")) { + subcategory = (String)p_custom_node->call("_get_subcategory"); + } + if (!subcategory.is_empty()) { + category += "/" + subcategory; + } + dict["category"] = category; + + bool highend = false; + if (p_custom_node->has_method("_is_highend")) { + highend = (bool)p_custom_node->call("_is_highend"); + } + dict["highend"] = highend; + + return dict; +} + +void VisualShaderEditor::_get_current_mode_limits(int &r_begin_type, int &r_end_type) const { + switch (visual_shader->get_mode()) { + case Shader::MODE_CANVAS_ITEM: + case Shader::MODE_SPATIAL: { + r_begin_type = 0; + r_end_type = 3; + } break; + case Shader::MODE_PARTICLES: { + r_begin_type = 3; + r_end_type = 5 + r_begin_type; + } break; + case Shader::MODE_SKY: { + r_begin_type = 8; + r_end_type = 1 + r_begin_type; + } break; + case Shader::MODE_FOG: { + r_begin_type = 9; + r_end_type = 1 + r_begin_type; + } break; + default: { + } break; + } +} + +void VisualShaderEditor::_script_created(const Ref<Script> &p_script) { + if (p_script.is_null() || p_script->get_instance_base_type() != "VisualShaderNodeCustom") { + return; + } + Ref<VisualShaderNodeCustom> ref; + ref.instantiate(); + ref->set_script(p_script); + + Dictionary dict = get_custom_node_data(ref); + add_custom_type(dict["name"], dict["script"], dict["description"], dict["return_icon_type"], dict["category"], dict["highend"]); + + _update_options_menu(); +} + +void VisualShaderEditor::_update_custom_script(const Ref<Script> &p_script) { + if (p_script.is_null() || p_script->get_instance_base_type() != "VisualShaderNodeCustom") { + return; + } + + Ref<VisualShaderNodeCustom> ref; + ref.instantiate(); + ref->set_script(p_script); + if (!ref->is_available(visual_shader->get_mode(), visual_shader->get_shader_type())) { + for (int i = 0; i < add_options.size(); i++) { + if (add_options[i].is_custom && add_options[i].script == p_script) { + add_options.remove_at(i); + _update_options_menu(); + // TODO: Make indication for the existed custom nodes with that script on graph to be disabled. + break; + } + } + return; + } + Dictionary dict = get_custom_node_data(ref); + + bool found_type = false; + bool need_rebuild = false; + + for (int i = custom_node_option_idx; i < add_options.size(); i++) { + if (add_options[i].script == p_script) { + found_type = true; + + add_options.write[i].name = dict["name"]; + add_options.write[i].return_type = dict["return_icon_type"]; + add_options.write[i].description = dict["description"]; + add_options.write[i].category = dict["category"]; + add_options.write[i].highend = dict["highend"]; + + int begin_type = 0; + int end_type = 0; + _get_current_mode_limits(begin_type, end_type); + + for (int t = begin_type; t < end_type; t++) { + VisualShader::Type type = (VisualShader::Type)t; + Vector<int> nodes = visual_shader->get_node_list(type); + + List<VisualShader::Connection> node_connections; + visual_shader->get_node_connections(type, &node_connections); + + List<VisualShader::Connection> custom_node_input_connections; + List<VisualShader::Connection> custom_node_output_connections; + + for (const VisualShader::Connection &E : node_connections) { + int from = E.from_node; + int from_port = E.from_port; + int to = E.to_node; + int to_port = E.to_port; + + if (graph_plugin->get_node_script(from) == p_script) { + custom_node_output_connections.push_back({ from, from_port, to, to_port }); + } else if (graph_plugin->get_node_script(to) == p_script) { + custom_node_input_connections.push_back({ from, from_port, to, to_port }); + } + } + + for (int node_id : nodes) { + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, node_id); + if (vsnode.is_null()) { + continue; + } + Ref<VisualShaderNodeCustom> custom_node = Ref<VisualShaderNodeCustom>(vsnode.ptr()); + if (custom_node.is_null() || custom_node->get_script() != p_script) { + continue; + } + need_rebuild = true; + + // Removes invalid connections. + { + int prev_input_port_count = custom_node->get_input_port_count(); + int prev_output_port_count = custom_node->get_output_port_count(); + + custom_node->update_ports(); + + int input_port_count = custom_node->get_input_port_count(); + int output_port_count = custom_node->get_output_port_count(); + + if (output_port_count != prev_output_port_count) { + for (const VisualShader::Connection &E : custom_node_output_connections) { + int from = E.from_node; + int from_idx = E.from_port; + int to = E.to_node; + int to_idx = E.to_port; + + if (from_idx >= output_port_count) { + visual_shader->disconnect_nodes(type, from, from_idx, to, to_idx); + graph_plugin->disconnect_nodes(type, from, from_idx, to, to_idx); + } + } + } + if (input_port_count != prev_input_port_count) { + for (const VisualShader::Connection &E : custom_node_input_connections) { + int from = E.from_node; + int from_idx = E.from_port; + int to = E.to_node; + int to_idx = E.to_port; + + if (to_idx >= input_port_count) { + visual_shader->disconnect_nodes(type, from, from_idx, to, to_idx); + graph_plugin->disconnect_nodes(type, from, from_idx, to, to_idx); + } + } + } + } + + graph_plugin->update_node(type, node_id); + } + } + break; + } + } + + if (!found_type) { + add_custom_type(dict["name"], dict["script"], dict["description"], dict["return_icon_type"], dict["category"], dict["highend"]); + } + + // To prevent updating options multiple times when multiple scripts are saved. + if (!_block_update_options_menu) { + _block_update_options_menu = true; + + call_deferred(SNAME("_update_options_menu_deferred")); + } + + // To prevent rebuilding the shader multiple times when multiple scripts are saved. + if (need_rebuild && !_block_rebuild_shader) { + _block_rebuild_shader = true; + + call_deferred(SNAME("_rebuild_shader_deferred")); + } +} + +void VisualShaderEditor::_resource_saved(const Ref<Resource> &p_resource) { + _update_custom_script(Ref<Script>(p_resource.ptr())); +} + +void VisualShaderEditor::_resources_removed() { + bool has_any_instance = false; + + for (const Ref<Script> &scr : custom_scripts_to_delete) { + for (int i = custom_node_option_idx; i < add_options.size(); i++) { + if (add_options[i].script == scr) { + add_options.remove_at(i); + + // Removes all node instances using that script from the graph. + { + int begin_type = 0; + int end_type = 0; + _get_current_mode_limits(begin_type, end_type); + + for (int t = begin_type; t < end_type; t++) { + VisualShader::Type type = (VisualShader::Type)t; + + List<VisualShader::Connection> node_connections; + visual_shader->get_node_connections(type, &node_connections); + + for (const VisualShader::Connection &E : node_connections) { + int from = E.from_node; + int from_port = E.from_port; + int to = E.to_node; + int to_port = E.to_port; + + if (graph_plugin->get_node_script(from) == scr || graph_plugin->get_node_script(to) == scr) { + visual_shader->disconnect_nodes(type, from, from_port, to, to_port); + graph_plugin->disconnect_nodes(type, from, from_port, to, to_port); + } + } + + Vector<int> nodes = visual_shader->get_node_list(type); + for (int node_id : nodes) { + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, node_id); + if (vsnode.is_null()) { + continue; + } + Ref<VisualShaderNodeCustom> custom_node = Ref<VisualShaderNodeCustom>(vsnode.ptr()); + if (custom_node.is_null() || custom_node->get_script() != scr) { + continue; + } + visual_shader->remove_node(type, node_id); + graph_plugin->remove_node(type, node_id, false); + + has_any_instance = true; + } + } + } + + break; + } + } + } + if (has_any_instance) { + EditorUndoRedoManager::get_singleton()->clear_history(); // Need to clear undo history, otherwise it may lead to hard-detected errors and crashes (since the script was removed). + ResourceSaver::save(visual_shader, visual_shader->get_path()); + } + _update_options_menu(); + + custom_scripts_to_delete.clear(); + pending_custom_scripts_to_delete = false; +} + +void VisualShaderEditor::_resource_removed(const Ref<Resource> &p_resource) { + Ref<Script> scr = Ref<Script>(p_resource.ptr()); + if (scr.is_null() || scr->get_instance_base_type() != "VisualShaderNodeCustom") { + return; + } + + custom_scripts_to_delete.push_back(scr); + + if (!pending_custom_scripts_to_delete) { + pending_custom_scripts_to_delete = true; + + call_deferred("_resources_removed"); + } +} + +void VisualShaderEditor::_update_options_menu_deferred() { + _update_options_menu(); + + _block_update_options_menu = false; +} + +void VisualShaderEditor::_rebuild_shader_deferred() { + if (visual_shader.is_valid()) { + visual_shader->rebuild(); + } + + _block_rebuild_shader = false; +} + bool VisualShaderEditor::_is_available(int p_mode) { int current_mode = edit_type->get_selected(); @@ -1130,10 +1593,7 @@ bool VisualShaderEditor::_is_available(int p_mode) { return (p_mode == -1 || (p_mode & current_mode) != 0); } -void VisualShaderEditor::update_custom_nodes() { - if (members_dialog->is_visible()) { - return; - } +void VisualShaderEditor::_update_nodes() { clear_custom_types(); List<StringName> class_list; ScriptServer::get_global_class_list(&class_list); @@ -1144,64 +1604,46 @@ void VisualShaderEditor::update_custom_nodes() { Ref<Resource> res = ResourceLoader::load(script_path); ERR_FAIL_COND(res.is_null()); ERR_FAIL_COND(!res->is_class("Script")); - Ref<Script> script = Ref<Script>(res); + Ref<Script> scr = Ref<Script>(res); Ref<VisualShaderNodeCustom> ref; ref.instantiate(); - ref->set_script(script); - - String name; - if (ref->has_method("_get_name")) { - name = (String)ref->call("_get_name"); - } else { - name = "Unnamed"; - } - - String description = ""; - if (ref->has_method("_get_description")) { - description = (String)ref->call("_get_description"); - } - - int return_icon_type = -1; - if (ref->has_method("_get_return_icon_type")) { - return_icon_type = (int)ref->call("_get_return_icon_type"); + ref->set_script(scr); + if (!ref->is_available(visual_shader->get_mode(), visual_shader->get_shader_type())) { + continue; } + Dictionary dict = get_custom_node_data(ref); - String category = ""; - if (ref->has_method("_get_category")) { - category = (String)ref->call("_get_category"); - } + String key; + key = String(dict["category"]) + "/" + String(dict["name"]); - String subcategory = ""; - if (ref->has_method("_get_subcategory")) { - subcategory = (String)ref->call("_get_subcategory"); - } + added[key] = dict; + } + } - bool highend = false; - if (ref->has_method("_is_highend")) { - highend = (bool)ref->call("_is_highend"); - } + // Disables not-supported copied items. + { + for (CopyItem &item : copy_items_buffer) { + Ref<VisualShaderNodeCustom> custom = Object::cast_to<VisualShaderNodeCustom>(item.node.ptr()); - Dictionary dict; - dict["name"] = name; - dict["script"] = script; - dict["description"] = description; - dict["return_icon_type"] = return_icon_type; - - category = category.rstrip("/"); - category = category.lstrip("/"); - category = "Addons/" + category; - if (!subcategory.is_empty()) { - category += "/" + subcategory; + if (custom.is_valid()) { + if (!custom->is_available(visual_shader->get_mode(), visual_shader->get_shader_type())) { + item.disabled = true; + } else { + item.disabled = false; + } + } else { + for (int i = 0; i < add_options.size(); i++) { + if (add_options[i].type == item.node->get_class_name()) { + if ((add_options[i].func != visual_shader->get_mode() && add_options[i].func != -1) || !_is_available(add_options[i].mode)) { + item.disabled = true; + } else { + item.disabled = false; + } + break; + } + } } - - dict["category"] = category; - dict["highend"] = highend; - - String key; - key = category + "/" + name; - - added[key] = dict; } } @@ -1238,9 +1680,9 @@ void VisualShaderEditor::_update_options_menu() { Color unsupported_color = get_theme_color(SNAME("error_color"), SNAME("Editor")); Color supported_color = get_theme_color(SNAME("warning_color"), SNAME("Editor")); - static bool low_driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name") == "opengl3"; + static bool low_driver = GLOBAL_GET("rendering/renderer/rendering_method") == "gl_compatibility"; - Map<String, TreeItem *> folders; + HashMap<String, TreeItem *> folders; int current_func = -1; @@ -1277,7 +1719,9 @@ void VisualShaderEditor::_update_options_menu() { if (input.is_valid()) { input->set_shader_mode(visual_shader->get_mode()); input->set_shader_type(visual_shader->get_shader_type()); - input->set_input_name(add_options[i].sub_func_str); + if (!add_options[i].ops.is_empty() && add_options[i].ops[0].get_type() == Variant::STRING) { + input->set_input_name((String)add_options[i].ops[0]); + } } Ref<VisualShaderNodeExpression> expression = Object::cast_to<VisualShaderNodeExpression>(vsn.ptr()); @@ -1287,13 +1731,13 @@ void VisualShaderEditor::_update_options_menu() { } } - Ref<VisualShaderNodeUniformRef> uniform_ref = Object::cast_to<VisualShaderNodeUniformRef>(vsn.ptr()); - if (uniform_ref.is_valid()) { + Ref<VisualShaderNodeParameterRef> parameter_ref = Object::cast_to<VisualShaderNodeParameterRef>(vsn.ptr()); + if (parameter_ref.is_valid()) { check_result = -1; if (members_input_port_type != VisualShaderNode::PORT_TYPE_MAX) { - for (int j = 0; j < uniform_ref->get_uniforms_count(); j++) { - if (visual_shader->is_port_types_compatible(uniform_ref->get_port_type_by_index(j), members_input_port_type)) { + for (int j = 0; j < parameter_ref->get_parameters_count(); j++) { + if (visual_shader->is_port_types_compatible(parameter_ref->get_port_type_by_index(j), members_input_port_type)) { check_result = 1; break; } @@ -1394,9 +1838,18 @@ void VisualShaderEditor::_update_options_menu() { case VisualShaderNode::PORT_TYPE_SCALAR_INT: item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("int"), SNAME("EditorIcons"))); break; - case VisualShaderNode::PORT_TYPE_VECTOR: + case VisualShaderNode::PORT_TYPE_SCALAR_UINT: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("uint"), SNAME("EditorIcons"))); + break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons"))); + break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons"))); break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector4"), SNAME("EditorIcons"))); + break; case VisualShaderNode::PORT_TYPE_BOOLEAN: item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("bool"), SNAME("EditorIcons"))); break; @@ -1421,6 +1874,7 @@ void VisualShaderEditor::_set_mode(int p_which) { edit_type_fog->set_visible(false); edit_type = edit_type_sky; custom_mode_box->set_visible(false); + varying_button->hide(); mode = MODE_FLAGS_SKY; } else if (p_which == VisualShader::MODE_FOG) { edit_type_standard->set_visible(false); @@ -1429,6 +1883,7 @@ void VisualShaderEditor::_set_mode(int p_which) { edit_type_fog->set_visible(true); edit_type = edit_type_fog; custom_mode_box->set_visible(false); + varying_button->hide(); mode = MODE_FLAGS_FOG; } else if (p_which == VisualShader::MODE_PARTICLES) { edit_type_standard->set_visible(false); @@ -1441,6 +1896,7 @@ void VisualShaderEditor::_set_mode(int p_which) { } else { custom_mode_box->set_visible(true); } + varying_button->hide(); mode = MODE_FLAGS_PARTICLES; } else { edit_type_particles->set_visible(false); @@ -1449,6 +1905,7 @@ void VisualShaderEditor::_set_mode(int p_which) { edit_type_fog->set_visible(false); edit_type = edit_type_standard; custom_mode_box->set_visible(false); + varying_button->show(); mode = MODE_FLAGS_SPATIAL_CANVASITEM; } visual_shader->set_shader_type(get_current_shader_type()); @@ -1480,60 +1937,70 @@ void VisualShaderEditor::_update_created_node(GraphNode *node) { node->add_theme_color_override("resizer_color", c); } -void VisualShaderEditor::_update_uniforms(bool p_update_refs) { - VisualShaderNodeUniformRef::clear_uniforms(); +void VisualShaderEditor::_update_parameters(bool p_update_refs) { + VisualShaderNodeParameterRef::clear_parameters(visual_shader->get_rid()); for (int t = 0; t < VisualShader::TYPE_MAX; t++) { Vector<int> tnodes = visual_shader->get_node_list((VisualShader::Type)t); for (int i = 0; i < tnodes.size(); i++) { Ref<VisualShaderNode> vsnode = visual_shader->get_node((VisualShader::Type)t, tnodes[i]); - Ref<VisualShaderNodeUniform> uniform = vsnode; - - if (uniform.is_valid()) { - Ref<VisualShaderNodeFloatUniform> float_uniform = vsnode; - Ref<VisualShaderNodeIntUniform> int_uniform = vsnode; - Ref<VisualShaderNodeVec3Uniform> vec3_uniform = vsnode; - Ref<VisualShaderNodeColorUniform> color_uniform = vsnode; - Ref<VisualShaderNodeBooleanUniform> bool_uniform = vsnode; - Ref<VisualShaderNodeTransformUniform> transform_uniform = vsnode; - - VisualShaderNodeUniformRef::UniformType uniform_type; - if (float_uniform.is_valid()) { - uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_FLOAT; - } else if (int_uniform.is_valid()) { - uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_INT; - } else if (bool_uniform.is_valid()) { - uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_BOOLEAN; - } else if (vec3_uniform.is_valid()) { - uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_VECTOR; - } else if (transform_uniform.is_valid()) { - uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_TRANSFORM; - } else if (color_uniform.is_valid()) { - uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_COLOR; + Ref<VisualShaderNodeParameter> parameter = vsnode; + + if (parameter.is_valid()) { + Ref<VisualShaderNodeFloatParameter> float_parameter = vsnode; + Ref<VisualShaderNodeIntParameter> int_parameter = vsnode; + Ref<VisualShaderNodeUIntParameter> uint_parameter = vsnode; + Ref<VisualShaderNodeVec2Parameter> vec2_parameter = vsnode; + Ref<VisualShaderNodeVec3Parameter> vec3_parameter = vsnode; + Ref<VisualShaderNodeVec4Parameter> vec4_parameter = vsnode; + Ref<VisualShaderNodeColorParameter> color_parameter = vsnode; + Ref<VisualShaderNodeBooleanParameter> boolean_parameter = vsnode; + Ref<VisualShaderNodeTransformParameter> transform_parameter = vsnode; + + VisualShaderNodeParameterRef::ParameterType parameter_type; + if (float_parameter.is_valid()) { + parameter_type = VisualShaderNodeParameterRef::PARAMETER_TYPE_FLOAT; + } else if (int_parameter.is_valid()) { + parameter_type = VisualShaderNodeParameterRef::PARAMETER_TYPE_INT; + } else if (uint_parameter.is_valid()) { + parameter_type = VisualShaderNodeParameterRef::PARAMETER_TYPE_UINT; + } else if (boolean_parameter.is_valid()) { + parameter_type = VisualShaderNodeParameterRef::PARAMETER_TYPE_BOOLEAN; + } else if (vec2_parameter.is_valid()) { + parameter_type = VisualShaderNodeParameterRef::PARAMETER_TYPE_VECTOR2; + } else if (vec3_parameter.is_valid()) { + parameter_type = VisualShaderNodeParameterRef::PARAMETER_TYPE_VECTOR3; + } else if (vec4_parameter.is_valid()) { + parameter_type = VisualShaderNodeParameterRef::PARAMETER_TYPE_VECTOR4; + } else if (transform_parameter.is_valid()) { + parameter_type = VisualShaderNodeParameterRef::PARAMETER_TYPE_TRANSFORM; + } else if (color_parameter.is_valid()) { + parameter_type = VisualShaderNodeParameterRef::PARAMETER_TYPE_COLOR; } else { - uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_SAMPLER; + parameter_type = VisualShaderNodeParameterRef::UNIFORM_TYPE_SAMPLER; } - VisualShaderNodeUniformRef::add_uniform(uniform->get_uniform_name(), uniform_type); + VisualShaderNodeParameterRef::add_parameter(visual_shader->get_rid(), parameter->get_parameter_name(), parameter_type); } } } if (p_update_refs) { - graph_plugin->update_uniform_refs(); + graph_plugin->update_parameter_refs(); } } -void VisualShaderEditor::_update_uniform_refs(Set<String> &p_deleted_names) { +void VisualShaderEditor::_update_parameter_refs(HashSet<String> &p_deleted_names) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); for (int i = 0; i < VisualShader::TYPE_MAX; i++) { VisualShader::Type type = VisualShader::Type(i); Vector<int> nodes = visual_shader->get_node_list(type); for (int j = 0; j < nodes.size(); j++) { if (j > 0) { - Ref<VisualShaderNodeUniformRef> ref = visual_shader->get_node(type, nodes[j]); + Ref<VisualShaderNodeParameterRef> ref = visual_shader->get_node(type, nodes[j]); if (ref.is_valid()) { - if (p_deleted_names.has(ref->get_uniform_name())) { - undo_redo->add_do_method(ref.ptr(), "set_uniform_name", "[None]"); - undo_redo->add_undo_method(ref.ptr(), "set_uniform_name", ref->get_uniform_name()); + if (p_deleted_names.has(ref->get_parameter_name())) { + undo_redo->add_do_method(ref.ptr(), "set_parameter_name", "[None]"); + undo_redo->add_undo_method(ref.ptr(), "set_parameter_name", ref->get_parameter_name()); undo_redo->add_do_method(graph_plugin.ptr(), "update_node", VisualShader::Type(i), nodes[j]); undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", VisualShader::Type(i), nodes[j]); } @@ -1567,25 +2034,23 @@ void VisualShaderEditor::_update_graph() { } } - List<VisualShader::Connection> connections; - visual_shader->get_node_connections(type, &connections); - graph_plugin->set_connections(connections); + List<VisualShader::Connection> node_connections; + visual_shader->get_node_connections(type, &node_connections); + graph_plugin->set_connections(node_connections); Vector<int> nodes = visual_shader->get_node_list(type); - _update_uniforms(false); + _update_parameters(false); + _update_varyings(); graph_plugin->clear_links(); - graph_plugin->make_dirty(true); graph_plugin->update_theme(); for (int n_i = 0; n_i < nodes.size(); n_i++) { - graph_plugin->add_node(type, nodes[n_i]); + graph_plugin->add_node(type, nodes[n_i], false); } - graph_plugin->make_dirty(false); - - for (const VisualShader::Connection &E : connections) { + for (const VisualShader::Connection &E : node_connections) { int from = E.from_node; int from_idx = E.from_port; int to = E.to_node; @@ -1594,8 +2059,10 @@ void VisualShaderEditor::_update_graph() { graph->connect_node(itos(from), from_idx, itos(to), to_idx); } - float graph_minimap_opacity = EditorSettings::get_singleton()->get("editors/visual_editors/minimap_opacity"); + float graph_minimap_opacity = EDITOR_GET("editors/visual_editors/minimap_opacity"); graph->set_minimap_opacity(graph_minimap_opacity); + float graph_lines_curvature = EDITOR_GET("editors/visual_editors/lines_curvature"); + graph->set_connection_lines_curvature(graph_lines_curvature); } VisualShader::Type VisualShaderEditor::get_current_shader_type() const { @@ -1619,6 +2086,7 @@ void VisualShaderEditor::_add_input_port(int p_node, int p_port, int p_port_type return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Input Port")); undo_redo->add_do_method(node.ptr(), "add_input_port", p_port, p_port_type, p_name); undo_redo->add_undo_method(node.ptr(), "remove_input_port", p_port); @@ -1634,6 +2102,7 @@ void VisualShaderEditor::_add_output_port(int p_node, int p_port, int p_port_typ return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Output Port")); undo_redo->add_do_method(node.ptr(), "add_output_port", p_port, p_port_type, p_name); undo_redo->add_undo_method(node.ptr(), "remove_output_port", p_port); @@ -1649,6 +2118,7 @@ void VisualShaderEditor::_change_input_port_type(int p_type, int p_node, int p_p return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Change Input Port Type")); undo_redo->add_do_method(node.ptr(), "set_input_port_type", p_port, p_type); undo_redo->add_undo_method(node.ptr(), "set_input_port_type", p_port, node->get_input_port_type(p_port)); @@ -1664,6 +2134,7 @@ void VisualShaderEditor::_change_output_port_type(int p_type, int p_node, int p_ return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Change Output Port Type")); undo_redo->add_do_method(node.ptr(), "set_output_port_type", p_port, p_type); undo_redo->add_undo_method(node.ptr(), "set_output_port_type", p_port, node->get_output_port_type(p_port)); @@ -1692,11 +2163,10 @@ void VisualShaderEditor::_change_input_port_name(const String &p_text, Object *p return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Change Input Port Name")); undo_redo->add_do_method(node.ptr(), "set_input_port_name", p_port_id, validated_name); undo_redo->add_undo_method(node.ptr(), "set_input_port_name", p_port_id, node->get_input_port_name(p_port_id)); - undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, p_node_id); - undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type, p_node_id); undo_redo->commit_action(); } @@ -1720,11 +2190,10 @@ void VisualShaderEditor::_change_output_port_name(const String &p_text, Object * return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Change Output Port Name")); undo_redo->add_do_method(node.ptr(), "set_output_port_name", p_port_id, validated_name); undo_redo->add_undo_method(node.ptr(), "set_output_port_name", p_port_id, prev_name); - undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, p_node_id); - undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type, p_node_id); undo_redo->commit_action(); } @@ -1734,6 +2203,7 @@ void VisualShaderEditor::_expand_output_port(int p_node, int p_port, bool p_expa Ref<VisualShaderNode> node = visual_shader->get_node(type, p_node); ERR_FAIL_COND(!node.is_valid()); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (p_expand) { undo_redo->create_action(TTR("Expand Output Port")); } else { @@ -1744,53 +2214,63 @@ void VisualShaderEditor::_expand_output_port(int p_node, int p_port, bool p_expa undo_redo->add_undo_method(node.ptr(), "_set_output_port_expanded", p_port, !p_expand); int type_size = 0; - if (node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_VECTOR) { - type_size = 3; + switch (node->get_output_port_type(p_port)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + type_size = 2; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + type_size = 3; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + type_size = 4; + } break; + default: + break; } List<VisualShader::Connection> conns; visual_shader->get_node_connections(type, &conns); for (const VisualShader::Connection &E : conns) { - int from_node = E.from_node; - int from_port = E.from_port; - int to_node = E.to_node; - int to_port = E.to_port; + int cn_from_node = E.from_node; + int cn_from_port = E.from_port; + int cn_to_node = E.to_node; + int cn_to_port = E.to_port; - if (from_node == p_node) { + if (cn_from_node == p_node) { if (p_expand) { - if (from_port > p_port) { // reconnect ports after expanded ports - undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port); + if (cn_from_port > p_port) { // reconnect ports after expanded ports + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); - undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); - undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port + type_size, to_node, to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port + type_size, to_node, to_port); + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes_forced", type, cn_from_node, cn_from_port + type_size, cn_to_node, cn_to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port + type_size, cn_to_node, cn_to_port); - undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port + type_size, to_node, to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port + type_size, to_node, to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, cn_from_node, cn_from_port + type_size, cn_to_node, cn_to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port + type_size, cn_to_node, cn_to_port); } } else { - if (from_port > p_port + type_size) { // reconnect ports after expanded ports - undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port); + if (cn_from_port > p_port + type_size) { // reconnect ports after expanded ports + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); - undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); - undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_port - type_size, to_node, to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port - type_size, to_node, to_port); + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, cn_from_node, cn_from_port - type_size, cn_to_node, cn_to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port - type_size, cn_to_node, cn_to_port); - undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port - type_size, to_node, to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port - type_size, to_node, to_port); - } else if (from_port > p_port) { // disconnect component ports - undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, cn_from_node, cn_from_port - type_size, cn_to_node, cn_to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port - type_size, cn_to_node, cn_to_port); + } else if (cn_from_port > p_port) { // disconnect component ports + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); - undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); } } } @@ -1821,35 +2301,36 @@ void VisualShaderEditor::_remove_input_port(int p_node, int p_port) { return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove Input Port")); List<VisualShader::Connection> conns; visual_shader->get_node_connections(type, &conns); for (const VisualShader::Connection &E : conns) { - int from_node = E.from_node; - int from_port = E.from_port; - int to_node = E.to_node; - int to_port = E.to_port; - - if (to_node == p_node) { - if (to_port == p_port) { - undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port); - - undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port); - } else if (to_port > p_port) { - undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port); - - undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port); - - undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port - 1); - undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port - 1); - - undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port - 1); - undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port - 1); + int cn_from_node = E.from_node; + int cn_from_port = E.from_port; + int cn_to_node = E.to_node; + int cn_to_port = E.to_port; + + if (cn_to_node == p_node) { + if (cn_to_port == p_port) { + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + } else if (cn_to_port > p_port) { + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes_forced", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port - 1); + undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port - 1); + + undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port - 1); + undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port - 1); } } } @@ -1870,35 +2351,36 @@ void VisualShaderEditor::_remove_output_port(int p_node, int p_port) { return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Remove Output Port")); List<VisualShader::Connection> conns; visual_shader->get_node_connections(type, &conns); for (const VisualShader::Connection &E : conns) { - int from_node = E.from_node; - int from_port = E.from_port; - int to_node = E.to_node; - int to_port = E.to_port; - - if (from_node == p_node) { - if (from_port == p_port) { - undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port); - - undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port); - } else if (from_port > p_port) { - undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port); - - undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port); - - undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port - 1, to_node, to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port - 1, to_node, to_port); - - undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port - 1, to_node, to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port - 1, to_node, to_port); + int cn_from_node = E.from_node; + int cn_from_port = E.from_port; + int cn_to_node = E.to_node; + int cn_to_port = E.to_port; + + if (cn_from_node == p_node) { + if (cn_from_port == p_port) { + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + } else if (cn_from_port > p_port) { + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes_forced", type, cn_from_node, cn_from_port - 1, cn_to_node, cn_to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port - 1, cn_to_node, cn_to_port); + + undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, cn_from_node, cn_from_port - 1, cn_to_node, cn_to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port - 1, cn_to_node, cn_to_port); } } } @@ -1936,6 +2418,7 @@ void VisualShaderEditor::_expression_focus_out(Object *code_edit, int p_node) { return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Set VisualShader Expression")); undo_redo->add_do_method(node.ptr(), "set_expression", expression_box->get_text()); undo_redo->add_undo_method(node.ptr(), "set_expression", node->get_expression()); @@ -1980,10 +2463,8 @@ void VisualShaderEditor::_set_node_size(int p_type, int p_node, const Vector2 &p if (!expression_node.is_null() && text_box) { Size2 box_size = size; - if (gn != nullptr) { - if (box_size.x < 150 * EDSCALE || box_size.y < 0) { - box_size.x = gn->get_size().x; - } + if (box_size.x < 150 * EDSCALE || box_size.y < 0) { + box_size.x = gn->get_size().x; } box_size.x -= text_box->get_offset(SIDE_LEFT); box_size.x -= 28 * EDSCALE; @@ -2001,6 +2482,7 @@ void VisualShaderEditor::_node_resized(const Vector2 &p_new_size, int p_type, in return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Resize VisualShader Node"), UndoRedo::MERGE_ENDS); undo_redo->add_do_method(this, "_set_node_size", p_type, p_node, p_new_size); undo_redo->add_undo_method(this, "_set_node_size", p_type, p_node, node->get_size()); @@ -2017,6 +2499,7 @@ void VisualShaderEditor::_preview_select_port(int p_node, int p_port) { if (node->get_output_port_for_preview() == p_port) { p_port = -1; //toggle it } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(p_port == -1 ? TTR("Hide Port Preview") : TTR("Show Port Preview")); undo_redo->add_do_method(node.ptr(), "set_output_port_for_preview", p_port); undo_redo->add_undo_method(node.ptr(), "set_output_port_for_preview", prev_port); @@ -2062,6 +2545,7 @@ void VisualShaderEditor::_comment_title_popup_hide() { if (node->get_title() == comment_title_change_edit->get_text()) { return; // nothing changed - ignored } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Set Comment Node Title")); undo_redo->add_do_method(node.ptr(), "set_title", comment_title_change_edit->get_text()); undo_redo->add_undo_method(node.ptr(), "set_title", node->get_title()); @@ -2104,6 +2588,7 @@ void VisualShaderEditor::_comment_desc_popup_hide() { if (node->get_description() == comment_desc_change_edit->get_text()) { return; // nothing changed - ignored } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Set Comment Node Description")); undo_redo->add_do_method(node.ptr(), "set_description", comment_desc_change_edit->get_text()); undo_redo->add_undo_method(node.ptr(), "set_description", node->get_title()); @@ -2112,38 +2597,39 @@ void VisualShaderEditor::_comment_desc_popup_hide() { undo_redo->commit_action(); } -void VisualShaderEditor::_uniform_line_edit_changed(const String &p_text, int p_node_id) { +void VisualShaderEditor::_parameter_line_edit_changed(const String &p_text, int p_node_id) { VisualShader::Type type = get_current_shader_type(); - Ref<VisualShaderNodeUniform> node = visual_shader->get_node(type, p_node_id); + Ref<VisualShaderNodeParameter> node = visual_shader->get_node(type, p_node_id); ERR_FAIL_COND(!node.is_valid()); - String validated_name = visual_shader->validate_uniform_name(p_text, node); + String validated_name = visual_shader->validate_parameter_name(p_text, node); - if (validated_name == node->get_uniform_name()) { + if (validated_name == node->get_parameter_name()) { return; } - undo_redo->create_action(TTR("Set Uniform Name")); - undo_redo->add_do_method(node.ptr(), "set_uniform_name", validated_name); - undo_redo->add_undo_method(node.ptr(), "set_uniform_name", node->get_uniform_name()); - undo_redo->add_do_method(graph_plugin.ptr(), "set_uniform_name", type, p_node_id, validated_name); - undo_redo->add_undo_method(graph_plugin.ptr(), "set_uniform_name", type, p_node_id, node->get_uniform_name()); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Set Parameter Name")); + undo_redo->add_do_method(node.ptr(), "set_parameter_name", validated_name); + undo_redo->add_undo_method(node.ptr(), "set_parameter_name", node->get_parameter_name()); + undo_redo->add_do_method(graph_plugin.ptr(), "set_parameter_name", type, p_node_id, validated_name); + undo_redo->add_undo_method(graph_plugin.ptr(), "set_parameter_name", type, p_node_id, node->get_parameter_name()); undo_redo->add_do_method(graph_plugin.ptr(), "update_node_deferred", type, p_node_id); undo_redo->add_undo_method(graph_plugin.ptr(), "update_node_deferred", type, p_node_id); - undo_redo->add_do_method(this, "_update_uniforms", true); - undo_redo->add_undo_method(this, "_update_uniforms", true); + undo_redo->add_do_method(this, "_update_parameters", true); + undo_redo->add_undo_method(this, "_update_parameters", true); - Set<String> changed_names; - changed_names.insert(node->get_uniform_name()); - _update_uniform_refs(changed_names); + HashSet<String> changed_names; + changed_names.insert(node->get_parameter_name()); + _update_parameter_refs(changed_names); undo_redo->commit_action(); } -void VisualShaderEditor::_uniform_line_edit_focus_out(Object *line_edit, int p_node_id) { - _uniform_line_edit_changed(Object::cast_to<LineEdit>(line_edit)->get_text(), p_node_id); +void VisualShaderEditor::_parameter_line_edit_focus_out(Object *line_edit, int p_node_id) { + _parameter_line_edit_changed(Object::cast_to<LineEdit>(line_edit)->get_text(), p_node_id); } void VisualShaderEditor::_port_name_focus_out(Object *line_edit, int p_node_id, int p_port_id, bool p_output) { @@ -2154,52 +2640,95 @@ void VisualShaderEditor::_port_name_focus_out(Object *line_edit, int p_node_id, } } -void VisualShaderEditor::_port_edited() { +void VisualShaderEditor::_port_edited(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) { VisualShader::Type type = get_current_shader_type(); - - Variant value = property_editor->get_variant(); Ref<VisualShaderNode> vsn = visual_shader->get_node(type, editing_node); ERR_FAIL_COND(!vsn.is_valid()); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Set Input Default Port")); Ref<VisualShaderNodeCustom> custom = Object::cast_to<VisualShaderNodeCustom>(vsn.ptr()); if (custom.is_valid()) { - undo_redo->add_do_method(custom.ptr(), "_set_input_port_default_value", editing_port, value); + undo_redo->add_do_method(custom.ptr(), "_set_input_port_default_value", editing_port, p_value); undo_redo->add_undo_method(custom.ptr(), "_set_input_port_default_value", editing_port, vsn->get_input_port_default_value(editing_port)); } else { - undo_redo->add_do_method(vsn.ptr(), "set_input_port_default_value", editing_port, value); + undo_redo->add_do_method(vsn.ptr(), "set_input_port_default_value", editing_port, p_value); undo_redo->add_undo_method(vsn.ptr(), "set_input_port_default_value", editing_port, vsn->get_input_port_default_value(editing_port)); } - undo_redo->add_do_method(graph_plugin.ptr(), "set_input_port_default_value", type, editing_node, editing_port, value); + undo_redo->add_do_method(graph_plugin.ptr(), "set_input_port_default_value", type, editing_node, editing_port, p_value); undo_redo->add_undo_method(graph_plugin.ptr(), "set_input_port_default_value", type, editing_node, editing_port, vsn->get_input_port_default_value(editing_port)); undo_redo->commit_action(); - - property_editor->hide(); } void VisualShaderEditor::_edit_port_default_input(Object *p_button, int p_node, int p_port) { VisualShader::Type type = get_current_shader_type(); + Ref<VisualShaderNode> vs_node = visual_shader->get_node(type, p_node); + Variant value = vs_node->get_input_port_default_value(p_port); + + edited_property_holder->set_edited_property(value); + + if (property_editor) { + property_editor->disconnect("property_changed", callable_mp(this, &VisualShaderEditor::_port_edited)); + property_editor_popup->remove_child(property_editor); + } + + // TODO: Define these properties with actual PropertyInfo and feed it to the property editor widget. + property_editor = EditorInspector::instantiate_property_editor(edited_property_holder.ptr(), value.get_type(), "edited_property", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE); + if (property_editor) { + property_editor->set_object_and_property(edited_property_holder.ptr(), "edited_property"); + property_editor->update_property(); + property_editor->set_name_split_ratio(0); + property_editor_popup->add_child(property_editor); + + property_editor->connect("property_changed", callable_mp(this, &VisualShaderEditor::_port_edited)); - Ref<VisualShaderNode> vsn = visual_shader->get_node(type, p_node); + Button *button = Object::cast_to<Button>(p_button); + if (button) { + property_editor_popup->set_position(button->get_screen_position() + Vector2(0, button->get_size().height) * graph->get_zoom()); + } + property_editor_popup->reset_size(); + if (button) { + property_editor_popup->popup(); + } else { + property_editor_popup->popup_centered_ratio(); + } + } - Button *button = Object::cast_to<Button>(p_button); - ERR_FAIL_COND(!button); - Variant value = vsn->get_input_port_default_value(p_port); - property_editor->set_position(button->get_screen_position() + Vector2(0, button->get_size().height)); - property_editor->edit(nullptr, "", value.get_type(), value, 0, ""); - property_editor->popup(); editing_node = p_node; editing_port = p_port; } -void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { +void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, const Vector<Variant> &p_ops) { + // INPUT + { + VisualShaderNodeInput *input = Object::cast_to<VisualShaderNodeInput>(p_node); + + if (input) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::STRING); + input->set_input_name((String)p_ops[0]); + return; + } + } + + // FLOAT_CONST + { + VisualShaderNodeFloatConstant *float_const = Object::cast_to<VisualShaderNodeFloatConstant>(p_node); + + if (float_const) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::FLOAT); + float_const->set_constant((float)p_ops[0]); + return; + } + } + // FLOAT_OP { VisualShaderNodeFloatOp *floatOp = Object::cast_to<VisualShaderNodeFloatOp>(p_node); if (floatOp) { - floatOp->set_operator((VisualShaderNodeFloatOp::Operator)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + floatOp->set_operator((VisualShaderNodeFloatOp::Operator)(int)p_ops[0]); return; } } @@ -2209,7 +2738,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeFloatFunc *floatFunc = Object::cast_to<VisualShaderNodeFloatFunc>(p_node); if (floatFunc) { - floatFunc->set_function((VisualShaderNodeFloatFunc::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + floatFunc->set_function((VisualShaderNodeFloatFunc::Function)(int)p_ops[0]); return; } } @@ -2219,7 +2749,10 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeVectorOp *vecOp = Object::cast_to<VisualShaderNodeVectorOp>(p_node); if (vecOp) { - vecOp->set_operator((VisualShaderNodeVectorOp::Operator)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + ERR_FAIL_COND(p_ops[1].get_type() != Variant::INT); + vecOp->set_operator((VisualShaderNodeVectorOp::Operator)(int)p_ops[0]); + vecOp->set_op_type((VisualShaderNodeVectorOp::OpType)(int)p_ops[1]); return; } } @@ -2229,7 +2762,10 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeVectorFunc *vecFunc = Object::cast_to<VisualShaderNodeVectorFunc>(p_node); if (vecFunc) { - vecFunc->set_function((VisualShaderNodeVectorFunc::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + ERR_FAIL_COND(p_ops[1].get_type() != Variant::INT); + vecFunc->set_function((VisualShaderNodeVectorFunc::Function)(int)p_ops[0]); + vecFunc->set_op_type((VisualShaderNodeVectorFunc::OpType)(int)p_ops[1]); return; } } @@ -2239,7 +2775,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeColorOp *colorOp = Object::cast_to<VisualShaderNodeColorOp>(p_node); if (colorOp) { - colorOp->set_operator((VisualShaderNodeColorOp::Operator)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + colorOp->set_operator((VisualShaderNodeColorOp::Operator)(int)p_ops[0]); return; } } @@ -2249,7 +2786,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeColorFunc *colorFunc = Object::cast_to<VisualShaderNodeColorFunc>(p_node); if (colorFunc) { - colorFunc->set_function((VisualShaderNodeColorFunc::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + colorFunc->set_function((VisualShaderNodeColorFunc::Function)(int)p_ops[0]); return; } } @@ -2259,7 +2797,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeIntOp *intOp = Object::cast_to<VisualShaderNodeIntOp>(p_node); if (intOp) { - intOp->set_operator((VisualShaderNodeIntOp::Operator)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + intOp->set_operator((VisualShaderNodeIntOp::Operator)(int)p_ops[0]); return; } } @@ -2269,7 +2808,30 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeIntFunc *intFunc = Object::cast_to<VisualShaderNodeIntFunc>(p_node); if (intFunc) { - intFunc->set_function((VisualShaderNodeIntFunc::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + intFunc->set_function((VisualShaderNodeIntFunc::Function)(int)p_ops[0]); + return; + } + } + + // UINT_OP + { + VisualShaderNodeUIntOp *uintOp = Object::cast_to<VisualShaderNodeUIntOp>(p_node); + + if (uintOp) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + uintOp->set_operator((VisualShaderNodeUIntOp::Operator)(int)p_ops[0]); + return; + } + } + + // UINT_FUNC + { + VisualShaderNodeUIntFunc *uintFunc = Object::cast_to<VisualShaderNodeUIntFunc>(p_node); + + if (uintFunc) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + uintFunc->set_function((VisualShaderNodeUIntFunc::Function)(int)p_ops[0]); return; } } @@ -2279,7 +2841,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeTransformOp *matOp = Object::cast_to<VisualShaderNodeTransformOp>(p_node); if (matOp) { - matOp->set_operator((VisualShaderNodeTransformOp::Operator)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + matOp->set_operator((VisualShaderNodeTransformOp::Operator)(int)p_ops[0]); return; } } @@ -2289,17 +2852,41 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeTransformFunc *matFunc = Object::cast_to<VisualShaderNodeTransformFunc>(p_node); if (matFunc) { - matFunc->set_function((VisualShaderNodeTransformFunc::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + matFunc->set_function((VisualShaderNodeTransformFunc::Function)(int)p_ops[0]); return; } } - //UV_FUNC + // VECTOR_COMPOSE + { + VisualShaderNodeVectorCompose *vecCompose = Object::cast_to<VisualShaderNodeVectorCompose>(p_node); + + if (vecCompose) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + vecCompose->set_op_type((VisualShaderNodeVectorCompose::OpType)(int)p_ops[0]); + return; + } + } + + // VECTOR_DECOMPOSE + { + VisualShaderNodeVectorDecompose *vecDecompose = Object::cast_to<VisualShaderNodeVectorDecompose>(p_node); + + if (vecDecompose) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + vecDecompose->set_op_type((VisualShaderNodeVectorDecompose::OpType)(int)p_ops[0]); + return; + } + } + + // UV_FUNC { VisualShaderNodeUVFunc *uvFunc = Object::cast_to<VisualShaderNodeUVFunc>(p_node); if (uvFunc) { - uvFunc->set_function((VisualShaderNodeUVFunc::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + uvFunc->set_function((VisualShaderNodeUVFunc::Function)(int)p_ops[0]); return; } } @@ -2309,7 +2896,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeIs *is = Object::cast_to<VisualShaderNodeIs>(p_node); if (is) { - is->set_function((VisualShaderNodeIs::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + is->set_function((VisualShaderNodeIs::Function)(int)p_ops[0]); return; } } @@ -2319,24 +2907,32 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeCompare *cmp = Object::cast_to<VisualShaderNodeCompare>(p_node); if (cmp) { - cmp->set_function((VisualShaderNodeCompare::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + cmp->set_function((VisualShaderNodeCompare::Function)(int)p_ops[0]); return; } } - // DERIVATIVE + // DISTANCE { - VisualShaderNodeScalarDerivativeFunc *sderFunc = Object::cast_to<VisualShaderNodeScalarDerivativeFunc>(p_node); + VisualShaderNodeVectorDistance *dist = Object::cast_to<VisualShaderNodeVectorDistance>(p_node); - if (sderFunc) { - sderFunc->set_function((VisualShaderNodeScalarDerivativeFunc::Function)p_op_idx); + if (dist) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + dist->set_op_type((VisualShaderNodeVectorDistance::OpType)(int)p_ops[0]); return; } + } - VisualShaderNodeVectorDerivativeFunc *vderFunc = Object::cast_to<VisualShaderNodeVectorDerivativeFunc>(p_node); + // DERIVATIVE + { + VisualShaderNodeDerivativeFunc *derFunc = Object::cast_to<VisualShaderNodeDerivativeFunc>(p_node); - if (vderFunc) { - vderFunc->set_function((VisualShaderNodeVectorDerivativeFunc::Function)p_op_idx); + if (derFunc) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + ERR_FAIL_COND(p_ops[1].get_type() != Variant::INT); + derFunc->set_function((VisualShaderNodeDerivativeFunc::Function)(int)p_ops[0]); + derFunc->set_op_type((VisualShaderNodeDerivativeFunc::OpType)(int)p_ops[1]); return; } } @@ -2346,7 +2942,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeMix *mix = Object::cast_to<VisualShaderNodeMix>(p_node); if (mix) { - mix->set_op_type((VisualShaderNodeMix::OpType)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + mix->set_op_type((VisualShaderNodeMix::OpType)(int)p_ops[0]); return; } } @@ -2356,7 +2953,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeClamp *clampFunc = Object::cast_to<VisualShaderNodeClamp>(p_node); if (clampFunc) { - clampFunc->set_op_type((VisualShaderNodeClamp::OpType)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + clampFunc->set_op_type((VisualShaderNodeClamp::OpType)(int)p_ops[0]); return; } } @@ -2366,7 +2964,28 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeSwitch *switchFunc = Object::cast_to<VisualShaderNodeSwitch>(p_node); if (switchFunc) { - switchFunc->set_op_type((VisualShaderNodeSwitch::OpType)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + switchFunc->set_op_type((VisualShaderNodeSwitch::OpType)(int)p_ops[0]); + return; + } + } + + // FACEFORWARD + { + VisualShaderNodeFaceForward *faceForward = Object::cast_to<VisualShaderNodeFaceForward>(p_node); + if (faceForward) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + faceForward->set_op_type((VisualShaderNodeFaceForward::OpType)(int)p_ops[0]); + return; + } + } + + // LENGTH + { + VisualShaderNodeVectorLen *length = Object::cast_to<VisualShaderNodeVectorLen>(p_node); + if (length) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + length->set_op_type((VisualShaderNodeVectorLen::OpType)(int)p_ops[0]); return; } } @@ -2376,7 +2995,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeSmoothStep *smoothStepFunc = Object::cast_to<VisualShaderNodeSmoothStep>(p_node); if (smoothStepFunc) { - smoothStepFunc->set_op_type((VisualShaderNodeSmoothStep::OpType)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + smoothStepFunc->set_op_type((VisualShaderNodeSmoothStep::OpType)(int)p_ops[0]); return; } } @@ -2386,7 +3006,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeStep *stepFunc = Object::cast_to<VisualShaderNodeStep>(p_node); if (stepFunc) { - stepFunc->set_op_type((VisualShaderNodeStep::OpType)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + stepFunc->set_op_type((VisualShaderNodeStep::OpType)(int)p_ops[0]); return; } } @@ -2396,12 +3017,13 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeMultiplyAdd *fmaFunc = Object::cast_to<VisualShaderNodeMultiplyAdd>(p_node); if (fmaFunc) { - fmaFunc->set_op_type((VisualShaderNodeMultiplyAdd::OpType)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + fmaFunc->set_op_type((VisualShaderNodeMultiplyAdd::OpType)(int)p_ops[0]); } } } -void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_path, int p_node_idx) { +void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, String p_resource_path, int p_node_idx) { ERR_FAIL_INDEX(p_idx, add_options.size()); VisualShader::Type type = get_current_shader_type(); @@ -2413,42 +3035,25 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa if (!is_custom && !add_options[p_idx].type.is_empty()) { VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instantiate(add_options[p_idx].type)); ERR_FAIL_COND(!vsn); - - VisualShaderNodeFloatConstant *constant = Object::cast_to<VisualShaderNodeFloatConstant>(vsn); - - if (constant) { - if ((int)add_options[p_idx].value != -1) { - constant->set_constant(add_options[p_idx].value); - } - } else { - if (p_op_idx != -1) { - VisualShaderNodeInput *input = Object::cast_to<VisualShaderNodeInput>(vsn); - - if (input) { - input->set_input_name(add_options[p_idx].sub_func_str); - } else { - _setup_node(vsn, p_op_idx); - } - } + if (!p_ops.is_empty()) { + _setup_node(vsn, p_ops); } - - VisualShaderNodeUniformRef *uniform_ref = Object::cast_to<VisualShaderNodeUniformRef>(vsn); - - if (uniform_ref && to_node != -1 && to_slot != -1) { + VisualShaderNodeParameterRef *parameter_ref = Object::cast_to<VisualShaderNodeParameterRef>(vsn); + if (parameter_ref && to_node != -1 && to_slot != -1) { VisualShaderNode::PortType input_port_type = visual_shader->get_node(type, to_node)->get_input_port_type(to_slot); bool success = false; - for (int i = 0; i < uniform_ref->get_uniforms_count(); i++) { - if (uniform_ref->get_port_type_by_index(i) == input_port_type) { - uniform_ref->set_uniform_name(uniform_ref->get_uniform_name_by_index(i)); + for (int i = 0; i < parameter_ref->get_parameters_count(); i++) { + if (parameter_ref->get_port_type_by_index(i) == input_port_type) { + parameter_ref->set_parameter_name(parameter_ref->get_parameter_name_by_index(i)); success = true; break; } } if (!success) { - for (int i = 0; i < uniform_ref->get_uniforms_count(); i++) { - if (visual_shader->is_port_types_compatible(uniform_ref->get_port_type_by_index(i), input_port_type)) { - uniform_ref->set_uniform_name(uniform_ref->get_uniform_name_by_index(i)); + for (int i = 0; i < parameter_ref->get_parameters_count(); i++) { + if (visual_shader->is_port_types_compatible(parameter_ref->get_port_type_by_index(i), input_port_type)) { + parameter_ref->set_parameter_name(parameter_ref->get_parameter_name_by_index(i)); break; } } @@ -2458,13 +3063,21 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa vsnode = Ref<VisualShaderNode>(vsn); } else { ERR_FAIL_COND(add_options[p_idx].script.is_null()); - String base_type = add_options[p_idx].script->get_instance_base_type(); + StringName base_type = add_options[p_idx].script->get_instance_base_type(); VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instantiate(base_type)); ERR_FAIL_COND(!vsn); vsnode = Ref<VisualShaderNode>(vsn); vsnode->set_script(add_options[p_idx].script); } + bool is_texture2d = (Object::cast_to<VisualShaderNodeTexture>(vsnode.ptr()) != nullptr); + bool is_texture3d = (Object::cast_to<VisualShaderNodeTexture3D>(vsnode.ptr()) != nullptr); + bool is_texture2d_array = (Object::cast_to<VisualShaderNodeTexture2DArray>(vsnode.ptr()) != nullptr); + bool is_cubemap = (Object::cast_to<VisualShaderNodeCubemap>(vsnode.ptr()) != nullptr); + bool is_curve = (Object::cast_to<VisualShaderNodeCurveTexture>(vsnode.ptr()) != nullptr); + bool is_curve_xyz = (Object::cast_to<VisualShaderNodeCurveXYZTexture>(vsnode.ptr()) != nullptr); + bool is_parameter = (Object::cast_to<VisualShaderNodeParameter>(vsnode.ptr()) != nullptr); + Point2 position = graph->get_scroll_ofs(); if (saved_node_pos_dirty) { @@ -2478,6 +3091,7 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa int id_to_use = visual_shader->get_valid_node_id(type); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (p_resource_path.is_empty()) { undo_redo->create_action(TTR("Add Node to Visual Shader")); } else { @@ -2485,12 +3099,12 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa } undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, vsnode, position, id_to_use); undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_to_use); - undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_to_use); - undo_redo->add_undo_method(graph_plugin.ptr(), "remove_node", type, id_to_use); + undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_to_use, false); + undo_redo->add_undo_method(graph_plugin.ptr(), "remove_node", type, id_to_use, false); VisualShaderNodeExpression *expr = Object::cast_to<VisualShaderNodeExpression>(vsnode.ptr()); if (expr) { - undo_redo->add_do_method(expr, "set_size", Size2(250 * EDSCALE, 150 * EDSCALE)); + expr->set_size(Size2(250 * EDSCALE, 150 * EDSCALE)); } bool created_expression_port = false; @@ -2499,9 +3113,7 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa VisualShaderNode::PortType input_port_type = visual_shader->get_node(type, to_node)->get_input_port_type(to_slot); if (expr && expr->is_editable() && input_port_type != VisualShaderNode::PORT_TYPE_SAMPLER) { - undo_redo->add_do_method(expr, "add_output_port", 0, input_port_type, "output0"); - undo_redo->add_undo_method(expr, "remove_output_port", 0); - + expr->add_output_port(0, input_port_type, "output0"); String initial_expression_code; switch (input_port_type) { @@ -2511,9 +3123,18 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa case VisualShaderNode::PORT_TYPE_SCALAR_INT: initial_expression_code = "output0 = 1;"; break; - case VisualShaderNode::PORT_TYPE_VECTOR: + case VisualShaderNode::PORT_TYPE_SCALAR_UINT: + initial_expression_code = "output0 = 1u;"; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_2D: + initial_expression_code = "output0 = vec2(1.0, 1.0);"; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: initial_expression_code = "output0 = vec3(1.0, 1.0, 1.0);"; break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: + initial_expression_code = "output0 = vec4(1.0, 1.0, 1.0, 1.0);"; + break; case VisualShaderNode::PORT_TYPE_BOOLEAN: initial_expression_code = "output0 = true;"; break; @@ -2524,16 +3145,15 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa break; } - undo_redo->add_do_method(expr, "set_expression", initial_expression_code); - undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, id_to_use); - + expr->set_expression(initial_expression_code); + expr->set_size(Size2(500 * EDSCALE, 200 * EDSCALE)); created_expression_port = true; } if (vsnode->get_output_port_count() > 0 || created_expression_port) { int _from_node = id_to_use; - int _from_slot = 0; if (created_expression_port) { + int _from_slot = 0; undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, _from_node, _from_slot, to_node, to_slot); undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, _from_node, _from_slot, to_node, to_slot); undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, _from_node, _from_slot, to_node, to_slot); @@ -2562,18 +3182,15 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa VisualShaderNode::PortType output_port_type = visual_shader->get_node(type, from_node)->get_output_port_type(from_slot); if (expr && expr->is_editable()) { - undo_redo->add_do_method(expr, "add_input_port", 0, output_port_type, "input0"); - undo_redo->add_undo_method(expr, "remove_input_port", 0); - undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, id_to_use); - + expr->add_input_port(0, output_port_type, "input0"); created_expression_port = true; } if (vsnode->get_input_port_count() > 0 || created_expression_port) { int _to_node = id_to_use; - int _to_slot = 0; if (created_expression_port) { + int _to_slot = 0; undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot); undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot); undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot); @@ -2590,23 +3207,32 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa } } } + + if (output_port_type == VisualShaderNode::PORT_TYPE_SAMPLER) { + if (is_texture2d) { + undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeTexture::SOURCE_PORT); + } + if (is_texture3d || is_texture2d_array) { + undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeSample3D::SOURCE_PORT); + } + if (is_cubemap) { + undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeCubemap::SOURCE_PORT); + } + } } } _member_cancel(); - VisualShaderNodeUniform *uniform = Object::cast_to<VisualShaderNodeUniform>(vsnode.ptr()); - if (uniform) { - undo_redo->add_do_method(this, "_update_uniforms", true); - undo_redo->add_undo_method(this, "_update_uniforms", true); + if (is_parameter) { + undo_redo->add_do_method(this, "_update_parameters", true); + undo_redo->add_undo_method(this, "_update_parameters", true); } - VisualShaderNodeCurveTexture *curve = Object::cast_to<VisualShaderNodeCurveTexture>(vsnode.ptr()); - if (curve) { + if (is_curve) { graph_plugin->call_deferred(SNAME("update_curve"), id_to_use); } - VisualShaderNodeCurveXYZTexture *curve_xyz = Object::cast_to<VisualShaderNodeCurveXYZTexture>(vsnode.ptr()); - if (curve_xyz) { + if (is_curve_xyz) { graph_plugin->call_deferred(SNAME("update_curve_xyz"), id_to_use); } @@ -2615,27 +3241,134 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa } else { //post-initialization - VisualShaderNodeTexture *texture2d = Object::cast_to<VisualShaderNodeTexture>(vsnode.ptr()); - VisualShaderNodeTexture3D *texture3d = Object::cast_to<VisualShaderNodeTexture3D>(vsnode.ptr()); - - if (texture2d || texture3d || curve || curve_xyz) { + if (is_texture2d || is_texture3d || is_curve || is_curve_xyz) { undo_redo->add_do_method(vsnode.ptr(), "set_texture", ResourceLoader::load(p_resource_path)); return; } - VisualShaderNodeCubemap *cubemap = Object::cast_to<VisualShaderNodeCubemap>(vsnode.ptr()); - if (cubemap) { + if (is_cubemap) { undo_redo->add_do_method(vsnode.ptr(), "set_cube_map", ResourceLoader::load(p_resource_path)); return; } - VisualShaderNodeTexture2DArray *texture2d_array = Object::cast_to<VisualShaderNodeTexture2DArray>(vsnode.ptr()); - if (texture2d_array) { + if (is_texture2d_array) { undo_redo->add_do_method(vsnode.ptr(), "set_texture_array", ResourceLoader::load(p_resource_path)); } } } +void VisualShaderEditor::_add_varying(const String &p_name, VisualShader::VaryingMode p_mode, VisualShader::VaryingType p_type) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(vformat(TTR("Add Varying to Visual Shader: %s"), p_name)); + + undo_redo->add_do_method(visual_shader.ptr(), "add_varying", p_name, p_mode, p_type); + undo_redo->add_undo_method(visual_shader.ptr(), "remove_varying", p_name); + + undo_redo->add_do_method(this, "_update_varyings"); + undo_redo->add_undo_method(this, "_update_varyings"); + + for (int i = 0; i <= VisualShader::TYPE_LIGHT; i++) { + if (p_mode == VisualShader::VARYING_MODE_FRAG_TO_LIGHT && i == 0) { + continue; + } + + VisualShader::Type type = VisualShader::Type(i); + Vector<int> nodes = visual_shader->get_node_list(type); + + for (int j = 0; j < nodes.size(); j++) { + int node_id = nodes[j]; + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, node_id); + Ref<VisualShaderNodeVarying> var = vsnode; + + if (var.is_valid()) { + undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, node_id); + undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type, node_id); + } + } + } + + undo_redo->add_do_method(this, "_update_varying_tree"); + undo_redo->add_undo_method(this, "_update_varying_tree"); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_remove_varying(const String &p_name) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(vformat(TTR("Remove Varying from Visual Shader: %s"), p_name)); + + VisualShader::VaryingMode var_mode = visual_shader->get_varying_mode(p_name); + + undo_redo->add_do_method(visual_shader.ptr(), "remove_varying", p_name); + undo_redo->add_undo_method(visual_shader.ptr(), "add_varying", p_name, var_mode, visual_shader->get_varying_type(p_name)); + + undo_redo->add_do_method(this, "_update_varyings"); + undo_redo->add_undo_method(this, "_update_varyings"); + + for (int i = 0; i <= VisualShader::TYPE_LIGHT; i++) { + if (var_mode == VisualShader::VARYING_MODE_FRAG_TO_LIGHT && i == 0) { + continue; + } + + VisualShader::Type type = VisualShader::Type(i); + Vector<int> nodes = visual_shader->get_node_list(type); + + for (int j = 0; j < nodes.size(); j++) { + int node_id = nodes[j]; + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, node_id); + Ref<VisualShaderNodeVarying> var = vsnode; + + if (var.is_valid()) { + String var_name = var->get_varying_name(); + + if (var_name == p_name) { + undo_redo->add_do_method(var.ptr(), "set_varying_name", "[None]"); + undo_redo->add_undo_method(var.ptr(), "set_varying_name", var_name); + undo_redo->add_do_method(var.ptr(), "set_varying_type", VisualShader::VARYING_TYPE_FLOAT); + undo_redo->add_undo_method(var.ptr(), "set_varying_type", var->get_varying_type()); + } + undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, node_id); + undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type, node_id); + } + } + + List<VisualShader::Connection> node_connections; + visual_shader->get_node_connections(type, &node_connections); + + for (VisualShader::Connection &E : node_connections) { + Ref<VisualShaderNodeVaryingGetter> var_getter = Object::cast_to<VisualShaderNodeVaryingGetter>(visual_shader->get_node(type, E.from_node).ptr()); + if (var_getter.is_valid() && E.from_port > 0) { + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + } + Ref<VisualShaderNodeVaryingSetter> var_setter = Object::cast_to<VisualShaderNodeVaryingSetter>(visual_shader->get_node(type, E.to_node).ptr()); + if (var_setter.is_valid() && E.to_port > 0) { + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + } + } + } + + undo_redo->add_do_method(this, "_update_varying_tree"); + undo_redo->add_undo_method(this, "_update_varying_tree"); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_update_varyings() { + VisualShaderNodeVarying::clear_varyings(); + + for (int i = 0; i < visual_shader->get_varyings_count(); i++) { + const VisualShader::Varying *var = visual_shader->get_varying_by_index(i); + + if (var != nullptr) { + VisualShaderNodeVarying::add_varying(var->name, var->mode, var->type); + } + } +} + void VisualShaderEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, int p_node) { VisualShader::Type type = get_current_shader_type(); drag_buffer.push_back({ type, p_node, p_from, p_to }); @@ -2648,6 +3381,7 @@ void VisualShaderEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_t void VisualShaderEditor::_nodes_dragged() { drag_dirty = false; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Node(s) Moved")); for (const DragOp &E : drag_buffer) { @@ -2671,6 +3405,7 @@ void VisualShaderEditor::_connection_request(const String &p_from, int p_from_in return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Nodes Connected")); List<VisualShader::Connection> conns; @@ -2702,6 +3437,7 @@ void VisualShaderEditor::_disconnection_request(const String &p_from, int p_from int from = p_from.to_int(); int to = p_to.to_int(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Nodes Disconnected")); undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from, p_from_index, to, p_to_index); undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, from, p_from_index, to, p_to_index); @@ -2741,6 +3477,7 @@ void VisualShaderEditor::_delete_nodes(int p_type, const List<int> &p_nodes) { List<VisualShader::Connection> conns; visual_shader->get_node_connections(type, &conns); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); for (const int &F : p_nodes) { for (const VisualShader::Connection &E : conns) { if (E.from_node == F || E.to_node == F) { @@ -2749,32 +3486,18 @@ void VisualShaderEditor::_delete_nodes(int p_type, const List<int> &p_nodes) { } } - Set<String> uniform_names; + HashSet<String> parameter_names; for (const int &F : p_nodes) { Ref<VisualShaderNode> node = visual_shader->get_node(type, F); undo_redo->add_do_method(visual_shader.ptr(), "remove_node", type, F); undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, node, visual_shader->get_node_position(type, F), F); - undo_redo->add_undo_method(graph_plugin.ptr(), "add_node", type, F); - - // restore size, inputs and outputs if node is group - VisualShaderNodeGroupBase *group = Object::cast_to<VisualShaderNodeGroupBase>(node.ptr()); - if (group) { - undo_redo->add_undo_method(group, "set_size", group->get_size()); - undo_redo->add_undo_method(group, "set_inputs", group->get_inputs()); - undo_redo->add_undo_method(group, "set_outputs", group->get_outputs()); - } - - // restore expression text if node is expression - VisualShaderNodeExpression *expression = Object::cast_to<VisualShaderNodeExpression>(node.ptr()); - if (expression) { - undo_redo->add_undo_method(expression, "set_expression", expression->get_expression()); - } + undo_redo->add_undo_method(graph_plugin.ptr(), "add_node", type, F, false); - VisualShaderNodeUniform *uniform = Object::cast_to<VisualShaderNodeUniform>(node.ptr()); - if (uniform) { - uniform_names.insert(uniform->get_uniform_name()); + VisualShaderNodeParameter *parameter = Object::cast_to<VisualShaderNodeParameter>(node.ptr()); + if (parameter) { + parameter_names.insert(parameter->get_parameter_name()); } } @@ -2800,19 +3523,20 @@ void VisualShaderEditor::_delete_nodes(int p_type, const List<int> &p_nodes) { // delete nodes from the graph for (const int &F : p_nodes) { - undo_redo->add_do_method(graph_plugin.ptr(), "remove_node", type, F); + undo_redo->add_do_method(graph_plugin.ptr(), "remove_node", type, F, false); } - // update uniform refs if any uniform has been deleted - if (uniform_names.size() > 0) { - undo_redo->add_do_method(this, "_update_uniforms", true); - undo_redo->add_undo_method(this, "_update_uniforms", true); + // update parameter refs if any parameter has been deleted + if (parameter_names.size() > 0) { + undo_redo->add_do_method(this, "_update_parameters", true); + undo_redo->add_undo_method(this, "_update_parameters", true); - _update_uniform_refs(uniform_names); + _update_parameter_refs(parameter_names); } } void VisualShaderEditor::_replace_node(VisualShader::Type p_type_id, int p_node_id, const StringName &p_from, const StringName &p_to) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->add_do_method(visual_shader.ptr(), "replace_node", p_type_id, p_node_id, p_to); undo_redo->add_undo_method(visual_shader.ptr(), "replace_node", p_type_id, p_node_id, p_from); } @@ -2827,37 +3551,38 @@ void VisualShaderEditor::_update_constant(VisualShader::Type p_type_id, int p_no } } -void VisualShaderEditor::_update_uniform(VisualShader::Type p_type_id, int p_node_id, Variant p_var, int p_preview_port) { - Ref<VisualShaderNodeUniform> uniform = visual_shader->get_node(p_type_id, p_node_id); - ERR_FAIL_COND(!uniform.is_valid()); +void VisualShaderEditor::_update_parameter(VisualShader::Type p_type_id, int p_node_id, Variant p_var, int p_preview_port) { + Ref<VisualShaderNodeParameter> parameter = visual_shader->get_node(p_type_id, p_node_id); + ERR_FAIL_COND(!parameter.is_valid()); - String valid_name = visual_shader->validate_uniform_name(uniform->get_uniform_name(), uniform); - uniform->set_uniform_name(valid_name); - graph_plugin->set_uniform_name(p_type_id, p_node_id, valid_name); + String valid_name = visual_shader->validate_parameter_name(parameter->get_parameter_name(), parameter); + parameter->set_parameter_name(valid_name); + graph_plugin->set_parameter_name(p_type_id, p_node_id, valid_name); - if (uniform->has_method("set_default_value_enabled")) { - uniform->call("set_default_value_enabled", true); - uniform->call("set_default_value", p_var); + if (parameter->has_method("set_default_value_enabled")) { + parameter->call("set_default_value_enabled", true); + parameter->call("set_default_value", p_var); } if (p_preview_port != -1) { - uniform->set_output_port_for_preview(p_preview_port); + parameter->set_output_port_for_preview(p_preview_port); } } -void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { +void VisualShaderEditor::_convert_constants_to_parameters(bool p_vice_versa) { VisualShader::Type type_id = get_current_shader_type(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (!p_vice_versa) { - undo_redo->create_action(TTR("Convert Constant Node(s) To Uniform(s)")); + undo_redo->create_action(TTR("Convert Constant Node(s) To Parameter(s)")); } else { - undo_redo->create_action(TTR("Convert Uniform Node(s) To Constant(s)")); + undo_redo->create_action(TTR("Convert Parameter Node(s) To Constant(s)")); } - const Set<int> ¤t_set = p_vice_versa ? selected_uniforms : selected_constants; - Set<String> deleted_names; + const HashSet<int> ¤t_set = p_vice_versa ? selected_parameters : selected_constants; + HashSet<String> deleted_names; - for (Set<int>::Element *E = current_set.front(); E; E = E->next()) { - int node_id = E->get(); + for (const int &E : current_set) { + int node_id = E; Ref<VisualShaderNode> node = visual_shader->get_node(type_id, node_id); bool caught = false; Variant var; @@ -2866,15 +3591,15 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { if (!p_vice_versa) { Ref<VisualShaderNodeFloatConstant> float_const = Object::cast_to<VisualShaderNodeFloatConstant>(node.ptr()); if (float_const.is_valid()) { - _replace_node(type_id, node_id, "VisualShaderNodeFloatConstant", "VisualShaderNodeFloatUniform"); + _replace_node(type_id, node_id, "VisualShaderNodeFloatConstant", "VisualShaderNodeFloatParameter"); var = float_const->get_constant(); caught = true; } } else { - Ref<VisualShaderNodeFloatUniform> float_uniform = Object::cast_to<VisualShaderNodeFloatUniform>(node.ptr()); - if (float_uniform.is_valid()) { - _replace_node(type_id, node_id, "VisualShaderNodeFloatUniform", "VisualShaderNodeFloatConstant"); - var = float_uniform->get_default_value(); + Ref<VisualShaderNodeFloatParameter> float_parameter = Object::cast_to<VisualShaderNodeFloatParameter>(node.ptr()); + if (float_parameter.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeFloatParameter", "VisualShaderNodeFloatConstant"); + var = float_parameter->get_default_value(); caught = true; } } @@ -2884,15 +3609,15 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { if (!p_vice_versa) { Ref<VisualShaderNodeIntConstant> int_const = Object::cast_to<VisualShaderNodeIntConstant>(node.ptr()); if (int_const.is_valid()) { - _replace_node(type_id, node_id, "VisualShaderNodeIntConstant", "VisualShaderNodeIntUniform"); + _replace_node(type_id, node_id, "VisualShaderNodeIntConstant", "VisualShaderNodeIntParameter"); var = int_const->get_constant(); caught = true; } } else { - Ref<VisualShaderNodeIntUniform> int_uniform = Object::cast_to<VisualShaderNodeIntUniform>(node.ptr()); - if (int_uniform.is_valid()) { - _replace_node(type_id, node_id, "VisualShaderNodeIntUniform", "VisualShaderNodeIntConstant"); - var = int_uniform->get_default_value(); + Ref<VisualShaderNodeIntParameter> int_parameter = Object::cast_to<VisualShaderNodeIntParameter>(node.ptr()); + if (int_parameter.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeIntParameter", "VisualShaderNodeIntConstant"); + var = int_parameter->get_default_value(); caught = true; } } @@ -2903,15 +3628,34 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { if (!p_vice_versa) { Ref<VisualShaderNodeBooleanConstant> boolean_const = Object::cast_to<VisualShaderNodeBooleanConstant>(node.ptr()); if (boolean_const.is_valid()) { - _replace_node(type_id, node_id, "VisualShaderNodeBooleanConstant", "VisualShaderNodeBooleanUniform"); + _replace_node(type_id, node_id, "VisualShaderNodeBooleanConstant", "VisualShaderNodeBooleanParameter"); var = boolean_const->get_constant(); caught = true; } } else { - Ref<VisualShaderNodeBooleanUniform> boolean_uniform = Object::cast_to<VisualShaderNodeBooleanUniform>(node.ptr()); - if (boolean_uniform.is_valid()) { - _replace_node(type_id, node_id, "VisualShaderNodeBooleanUniform", "VisualShaderNodeBooleanConstant"); - var = boolean_uniform->get_default_value(); + Ref<VisualShaderNodeBooleanParameter> boolean_parameter = Object::cast_to<VisualShaderNodeBooleanParameter>(node.ptr()); + if (boolean_parameter.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeBooleanParameter", "VisualShaderNodeBooleanConstant"); + var = boolean_parameter->get_default_value(); + caught = true; + } + } + } + + // vec2 + if (!caught) { + if (!p_vice_versa) { + Ref<VisualShaderNodeVec2Constant> vec2_const = Object::cast_to<VisualShaderNodeVec2Constant>(node.ptr()); + if (vec2_const.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeVec2Constant", "VisualShaderNodeVec2Parameter"); + var = vec2_const->get_constant(); + caught = true; + } + } else { + Ref<VisualShaderNodeVec2Parameter> vec2_parameter = Object::cast_to<VisualShaderNodeVec2Parameter>(node.ptr()); + if (vec2_parameter.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeVec2Parameter", "VisualShaderNodeVec2Constant"); + var = vec2_parameter->get_default_value(); caught = true; } } @@ -2922,15 +3666,34 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { if (!p_vice_versa) { Ref<VisualShaderNodeVec3Constant> vec3_const = Object::cast_to<VisualShaderNodeVec3Constant>(node.ptr()); if (vec3_const.is_valid()) { - _replace_node(type_id, node_id, "VisualShaderNodeVec3Constant", "VisualShaderNodeVec3Uniform"); + _replace_node(type_id, node_id, "VisualShaderNodeVec3Constant", "VisualShaderNodeVec3Parameter"); var = vec3_const->get_constant(); caught = true; } } else { - Ref<VisualShaderNodeVec3Uniform> vec3_uniform = Object::cast_to<VisualShaderNodeVec3Uniform>(node.ptr()); - if (vec3_uniform.is_valid()) { - _replace_node(type_id, node_id, "VisualShaderNodeVec3Uniform", "VisualShaderNodeVec3Constant"); - var = vec3_uniform->get_default_value(); + Ref<VisualShaderNodeVec3Parameter> vec3_parameter = Object::cast_to<VisualShaderNodeVec3Parameter>(node.ptr()); + if (vec3_parameter.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeVec3Parameter", "VisualShaderNodeVec3Constant"); + var = vec3_parameter->get_default_value(); + caught = true; + } + } + } + + // vec4 + if (!caught) { + if (!p_vice_versa) { + Ref<VisualShaderNodeVec4Constant> vec4_const = Object::cast_to<VisualShaderNodeVec4Constant>(node.ptr()); + if (vec4_const.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeVec4Constant", "VisualShaderNodeVec4Parameter"); + var = vec4_const->get_constant(); + caught = true; + } + } else { + Ref<VisualShaderNodeVec4Parameter> vec4_parameter = Object::cast_to<VisualShaderNodeVec4Parameter>(node.ptr()); + if (vec4_parameter.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeVec4Parameter", "VisualShaderNodeVec4Constant"); + var = vec4_parameter->get_default_value(); caught = true; } } @@ -2941,15 +3704,15 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { if (!p_vice_versa) { Ref<VisualShaderNodeColorConstant> color_const = Object::cast_to<VisualShaderNodeColorConstant>(node.ptr()); if (color_const.is_valid()) { - _replace_node(type_id, node_id, "VisualShaderNodeColorConstant", "VisualShaderNodeColorUniform"); + _replace_node(type_id, node_id, "VisualShaderNodeColorConstant", "VisualShaderNodeColorParameter"); var = color_const->get_constant(); caught = true; } } else { - Ref<VisualShaderNodeColorUniform> color_uniform = Object::cast_to<VisualShaderNodeColorUniform>(node.ptr()); - if (color_uniform.is_valid()) { - _replace_node(type_id, node_id, "VisualShaderNodeColorUniform", "VisualShaderNodeColorConstant"); - var = color_uniform->get_default_value(); + Ref<VisualShaderNodeColorParameter> color_parameter = Object::cast_to<VisualShaderNodeColorParameter>(node.ptr()); + if (color_parameter.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeColorParameter", "VisualShaderNodeColorConstant"); + var = color_parameter->get_default_value(); caught = true; } } @@ -2960,15 +3723,15 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { if (!p_vice_versa) { Ref<VisualShaderNodeTransformConstant> transform_const = Object::cast_to<VisualShaderNodeTransformConstant>(node.ptr()); if (transform_const.is_valid()) { - _replace_node(type_id, node_id, "VisualShaderNodeTransformConstant", "VisualShaderNodeTransformUniform"); + _replace_node(type_id, node_id, "VisualShaderNodeTransformConstant", "VisualShaderNodeTransformParameter"); var = transform_const->get_constant(); caught = true; } } else { - Ref<VisualShaderNodeTransformUniform> transform_uniform = Object::cast_to<VisualShaderNodeTransformUniform>(node.ptr()); - if (transform_uniform.is_valid()) { - _replace_node(type_id, node_id, "VisualShaderNodeTransformUniform", "VisualShaderNodeTransformConstant"); - var = transform_uniform->get_default_value(); + Ref<VisualShaderNodeTransformParameter> transform_parameter = Object::cast_to<VisualShaderNodeTransformParameter>(node.ptr()); + if (transform_parameter.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeTransformParameter", "VisualShaderNodeTransformConstant"); + var = transform_parameter->get_default_value(); caught = true; } } @@ -2977,27 +3740,27 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { int preview_port = node->get_output_port_for_preview(); if (!p_vice_versa) { - undo_redo->add_do_method(this, "_update_uniform", type_id, node_id, var, preview_port); + undo_redo->add_do_method(this, "_update_parameter", type_id, node_id, var, preview_port); undo_redo->add_undo_method(this, "_update_constant", type_id, node_id, var, preview_port); } else { undo_redo->add_do_method(this, "_update_constant", type_id, node_id, var, preview_port); - undo_redo->add_undo_method(this, "_update_uniform", type_id, node_id, var, preview_port); + undo_redo->add_undo_method(this, "_update_parameter", type_id, node_id, var, preview_port); - Ref<VisualShaderNodeUniform> uniform = Object::cast_to<VisualShaderNodeUniform>(node.ptr()); - ERR_CONTINUE(!uniform.is_valid()); + Ref<VisualShaderNodeParameter> parameter = Object::cast_to<VisualShaderNodeParameter>(node.ptr()); + ERR_CONTINUE(!parameter.is_valid()); - deleted_names.insert(uniform->get_uniform_name()); + deleted_names.insert(parameter->get_parameter_name()); } undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type_id, node_id); undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type_id, node_id); } - undo_redo->add_do_method(this, "_update_uniforms", true); - undo_redo->add_undo_method(this, "_update_uniforms", true); + undo_redo->add_do_method(this, "_update_parameters", true); + undo_redo->add_undo_method(this, "_update_parameters", true); if (deleted_names.size() > 0) { - _update_uniform_refs(deleted_names); + _update_parameter_refs(deleted_names); } undo_redo->commit_action(); @@ -3007,27 +3770,36 @@ void VisualShaderEditor::_delete_node_request(int p_type, int p_node) { List<int> to_erase; to_erase.push_back(p_node); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Delete VisualShader Node")); _delete_nodes(p_type, to_erase); undo_redo->commit_action(); } -void VisualShaderEditor::_delete_nodes_request() { +void VisualShaderEditor::_delete_nodes_request(const TypedArray<StringName> &p_nodes) { List<int> to_erase; - for (int i = 0; i < graph->get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); - if (gn) { - if (gn->is_selected() && gn->is_close_button_visible()) { - to_erase.push_back(gn->get_name().operator String().to_int()); + if (p_nodes.is_empty()) { + // Called from context menu. + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); + if (gn) { + if (gn->is_selected() && gn->is_close_button_visible()) { + to_erase.push_back(gn->get_name().operator String().to_int()); + } } } + } else { + for (int i = 0; i < p_nodes.size(); i++) { + to_erase.push_back(p_nodes[i].operator String().to_int()); + } } if (to_erase.is_empty()) { return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Delete VisualShader Node(s)")); _delete_nodes(get_current_shader_type(), to_erase); undo_redo->commit_action(); @@ -3054,7 +3826,7 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) { selected_constants.clear(); - selected_uniforms.clear(); + selected_parameters.clear(); selected_comment = -1; selected_float_constant = -1; @@ -3080,9 +3852,9 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { if (float_constant_node != nullptr) { selected_float_constant = id; } - VisualShaderNodeUniform *uniform_node = Object::cast_to<VisualShaderNodeUniform>(node.ptr()); - if (uniform_node != nullptr && uniform_node->is_convertible_to_constant()) { - selected_uniforms.insert(id); + VisualShaderNodeParameter *parameter_node = Object::cast_to<VisualShaderNodeParameter>(node.ptr()); + if (parameter_node != nullptr && parameter_node->is_convertible_to_constant()) { + selected_parameters.insert(id); } } } @@ -3093,15 +3865,23 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { selected_float_constant = -1; } - if (to_change.is_empty() && copy_items_buffer.is_empty()) { + bool copy_buffer_empty = true; + for (const CopyItem &item : copy_items_buffer) { + if (!item.disabled) { + copy_buffer_empty = false; + break; + } + } + + if (to_change.is_empty() && copy_buffer_empty) { _show_members_dialog(true); } else { popup_menu->set_item_disabled(NodeMenuOptions::CUT, to_change.is_empty()); popup_menu->set_item_disabled(NodeMenuOptions::COPY, to_change.is_empty()); - popup_menu->set_item_disabled(NodeMenuOptions::PASTE, copy_items_buffer.is_empty()); + popup_menu->set_item_disabled(NodeMenuOptions::PASTE, copy_buffer_empty); popup_menu->set_item_disabled(NodeMenuOptions::DELETE, to_change.is_empty()); popup_menu->set_item_disabled(NodeMenuOptions::DUPLICATE, to_change.is_empty()); - popup_menu->set_item_disabled(NodeMenuOptions::CLEAR_COPY_BUFFER, copy_items_buffer.is_empty()); + popup_menu->set_item_disabled(NodeMenuOptions::CLEAR_COPY_BUFFER, copy_buffer_empty); int temp = popup_menu->get_item_index(NodeMenuOptions::SEPARATOR2); if (temp != -1) { @@ -3111,11 +3891,11 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { if (temp != -1) { popup_menu->remove_item(temp); } - temp = popup_menu->get_item_index(NodeMenuOptions::CONVERT_CONSTANTS_TO_UNIFORMS); + temp = popup_menu->get_item_index(NodeMenuOptions::CONVERT_CONSTANTS_TO_PARAMETERS); if (temp != -1) { popup_menu->remove_item(temp); } - temp = popup_menu->get_item_index(NodeMenuOptions::CONVERT_UNIFORMS_TO_CONSTANTS); + temp = popup_menu->get_item_index(NodeMenuOptions::CONVERT_PARAMETERS_TO_CONSTANTS); if (temp != -1) { popup_menu->remove_item(temp); } @@ -3132,7 +3912,7 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { popup_menu->remove_item(temp); } - if (selected_constants.size() > 0 || selected_uniforms.size() > 0) { + if (selected_constants.size() > 0 || selected_parameters.size() > 0) { popup_menu->add_separator("", NodeMenuOptions::SEPARATOR2); if (selected_float_constant != -1) { @@ -3151,11 +3931,11 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { } if (selected_constants.size() > 0) { - popup_menu->add_item(TTR("Convert Constant(s) to Uniform(s)"), NodeMenuOptions::CONVERT_CONSTANTS_TO_UNIFORMS); + popup_menu->add_item(TTR("Convert Constant(s) to Parameter(s)"), NodeMenuOptions::CONVERT_CONSTANTS_TO_PARAMETERS); } - if (selected_uniforms.size() > 0) { - popup_menu->add_item(TTR("Convert Uniform(s) to Constant(s)"), NodeMenuOptions::CONVERT_UNIFORMS_TO_CONSTANTS); + if (selected_parameters.size() > 0) { + popup_menu->add_item(TTR("Convert Parameter(s) to Constant(s)"), NodeMenuOptions::CONVERT_PARAMETERS_TO_CONSTANTS); } } @@ -3203,6 +3983,43 @@ void VisualShaderEditor::_show_members_dialog(bool at_mouse_pos, VisualShaderNod node_filter->select_all(); } +void VisualShaderEditor::_varying_menu_id_pressed(int p_idx) { + switch (VaryingMenuOptions(p_idx)) { + case VaryingMenuOptions::ADD: { + _show_add_varying_dialog(); + } break; + case VaryingMenuOptions::REMOVE: { + _show_remove_varying_dialog(); + } break; + default: + break; + } +} + +void VisualShaderEditor::_show_add_varying_dialog() { + _varying_name_changed(varying_name->get_text()); + + add_varying_dialog->set_position(graph->get_screen_position() + varying_button->get_position() + Point2(5 * EDSCALE, 65 * EDSCALE)); + add_varying_dialog->popup(); + + // Keep dialog within window bounds. + Rect2 window_rect = Rect2(DisplayServer::get_singleton()->window_get_position(), DisplayServer::get_singleton()->window_get_size()); + Rect2 dialog_rect = Rect2(add_varying_dialog->get_position(), add_varying_dialog->get_size()); + Vector2 difference = (dialog_rect.get_end() - window_rect.get_end()).max(Vector2()); + add_varying_dialog->set_position(add_varying_dialog->get_position() - difference); +} + +void VisualShaderEditor::_show_remove_varying_dialog() { + remove_varying_dialog->set_position(graph->get_screen_position() + varying_button->get_position() + Point2(5 * EDSCALE, 65 * EDSCALE)); + remove_varying_dialog->popup(); + + // Keep dialog within window bounds. + Rect2 window_rect = Rect2(DisplayServer::get_singleton()->window_get_position(), DisplayServer::get_singleton()->window_get_size()); + Rect2 dialog_rect = Rect2(remove_varying_dialog->get_position(), remove_varying_dialog->get_size()); + Vector2 difference = (dialog_rect.get_end() - window_rect.get_end()).max(Vector2()); + remove_varying_dialog->set_position(remove_varying_dialog->get_position() - difference); +} + void VisualShaderEditor::_sbox_input(const Ref<InputEvent> &p_ie) { Ref<InputEventKey> ie = p_ie; if (ie.is_valid() && (ie->get_keycode() == Key::UP || ie->get_keycode() == Key::DOWN || ie->get_keycode() == Key::ENTER || ie->get_keycode() == Key::KP_ENTER)) { @@ -3212,86 +4029,103 @@ void VisualShaderEditor::_sbox_input(const Ref<InputEvent> &p_ie) { } void VisualShaderEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - node_filter->set_clear_button_enabled(true); - - // collapse tree by default - - TreeItem *category = members->get_root()->get_first_child(); - while (category) { - category->set_collapsed(true); - TreeItem *sub_category = category->get_first_child(); - while (sub_category) { - sub_category->set_collapsed(true); - sub_category = sub_category->get_next(); - } - category = category->get_next(); - } - } + switch (p_what) { + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); + graph->set_warped_panning(bool(EDITOR_GET("editors/panning/warped_mouse_panning"))); + graph->set_minimap_opacity(EDITOR_GET("editors/visual_editors/minimap_opacity")); + graph->set_connection_lines_curvature(EDITOR_GET("editors/visual_editors/lines_curvature")); + _update_graph(); + } break; - if (p_what == NOTIFICATION_DRAG_BEGIN) { - Dictionary dd = get_viewport()->gui_get_drag_data(); - if (members->is_visible_in_tree() && dd.has("id")) { - members->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM); - } - } else if (p_what == NOTIFICATION_DRAG_END) { - members->set_drop_mode_flags(0); - } + case NOTIFICATION_ENTER_TREE: { + node_filter->set_clear_button_enabled(true); - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - highend_label->set_modulate(get_theme_color(SNAME("vulkan_color"), SNAME("Editor"))); + // collapse tree by default - node_filter->set_right_icon(Control::get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); + TreeItem *category = members->get_root()->get_first_child(); + while (category) { + category->set_collapsed(true); + TreeItem *sub_category = category->get_first_child(); + while (sub_category) { + sub_category->set_collapsed(true); + sub_category = sub_category->get_next(); + } + category = category->get_next(); + } - preview_shader->set_icon(Control::get_theme_icon(SNAME("Shader"), SNAME("EditorIcons"))); + graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); + graph->set_warped_panning(bool(EDITOR_GET("editors/panning/warped_mouse_panning"))); + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + highend_label->set_modulate(get_theme_color(SNAME("highend_color"), SNAME("Editor"))); - { - Color background_color = EDITOR_GET("text_editor/theme/highlighting/background_color"); - Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color"); - Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); - Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); - Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); - Color symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color"); - Color function_color = EDITOR_GET("text_editor/theme/highlighting/function_color"); - Color number_color = EDITOR_GET("text_editor/theme/highlighting/number_color"); - Color members_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color"); + node_filter->set_right_icon(Control::get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - preview_text->add_theme_color_override("background_color", background_color); + preview_shader->set_icon(Control::get_theme_icon(SNAME("Shader"), SNAME("EditorIcons"))); - for (const String &E : keyword_list) { - if (ShaderLanguage::is_control_flow_keyword(E)) { - syntax_highlighter->add_keyword_color(E, control_flow_keyword_color); - } else { - syntax_highlighter->add_keyword_color(E, keyword_color); + { + Color background_color = EDITOR_GET("text_editor/theme/highlighting/background_color"); + Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color"); + Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); + Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); + Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); + Color symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color"); + Color function_color = EDITOR_GET("text_editor/theme/highlighting/function_color"); + Color number_color = EDITOR_GET("text_editor/theme/highlighting/number_color"); + Color members_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color"); + Color error_color = get_theme_color(SNAME("error_color"), SNAME("Editor")); + + preview_text->add_theme_color_override("background_color", background_color); + varying_error_label->add_theme_color_override("font_color", error_color); + + for (const String &E : keyword_list) { + if (ShaderLanguage::is_control_flow_keyword(E)) { + syntax_highlighter->add_keyword_color(E, control_flow_keyword_color); + } else { + syntax_highlighter->add_keyword_color(E, keyword_color); + } } - } - preview_text->add_theme_font_override("font", get_theme_font(SNAME("expression"), SNAME("EditorFonts"))); - preview_text->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts"))); - preview_text->add_theme_color_override("font_color", text_color); - syntax_highlighter->set_number_color(number_color); - syntax_highlighter->set_symbol_color(symbol_color); - syntax_highlighter->set_function_color(function_color); - syntax_highlighter->set_member_variable_color(members_color); - syntax_highlighter->clear_color_regions(); - syntax_highlighter->add_color_region("/*", "*/", comment_color, false); - syntax_highlighter->add_color_region("//", "", comment_color, true); + preview_text->add_theme_font_override("font", get_theme_font(SNAME("expression"), SNAME("EditorFonts"))); + preview_text->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts"))); + preview_text->add_theme_color_override("font_color", text_color); + syntax_highlighter->set_number_color(number_color); + syntax_highlighter->set_symbol_color(symbol_color); + syntax_highlighter->set_function_color(function_color); + syntax_highlighter->set_member_variable_color(members_color); + syntax_highlighter->clear_color_regions(); + syntax_highlighter->add_color_region("/*", "*/", comment_color, false); + syntax_highlighter->add_color_region("//", "", comment_color, true); + + preview_text->clear_comment_delimiters(); + preview_text->add_comment_delimiter("/*", "*/", false); + preview_text->add_comment_delimiter("//", "", true); + + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); + error_label->add_theme_font_override("font", get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); + error_label->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); + error_label->add_theme_color_override("font_color", error_color); + } - preview_text->clear_comment_delimiters(); - preview_text->add_comment_delimiter("/*", "*/", false); - preview_text->add_comment_delimiter("//", "", true); + tools->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); - error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); - error_label->add_theme_font_override("font", get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); - error_label->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); - error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); - } + if (p_what == NOTIFICATION_THEME_CHANGED && is_visible_in_tree()) { + _update_graph(); + } + } break; - tools->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); + case NOTIFICATION_DRAG_BEGIN: { + Dictionary dd = get_viewport()->gui_get_drag_data(); + if (members->is_visible_in_tree() && dd.has("id")) { + members->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM); + } + } break; - if (p_what == NOTIFICATION_THEME_CHANGED && is_visible_in_tree()) { - _update_graph(); - } + case NOTIFICATION_DRAG_END: { + members->set_drop_mode_flags(0); + } break; } } @@ -3320,7 +4154,7 @@ void VisualShaderEditor::_dup_copy_nodes(int p_type, List<CopyItem> &r_items, Li selection_center.x = 0.0f; selection_center.y = 0.0f; - Set<int> nodes; + HashSet<int> nodes; for (int i = 0; i < graph->get_child_count(); i++) { GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); @@ -3365,10 +4199,10 @@ void VisualShaderEditor::_dup_copy_nodes(int p_type, List<CopyItem> &r_items, Li } } - List<VisualShader::Connection> connections; - visual_shader->get_node_connections(type, &connections); + List<VisualShader::Connection> node_connections; + visual_shader->get_node_connections(type, &node_connections); - for (const VisualShader::Connection &E : connections) { + for (const VisualShader::Connection &E : node_connections) { if (nodes.has(E.from_node) && nodes.has(E.to_node)) { r_connections.push_back(E); } @@ -3378,9 +4212,21 @@ void VisualShaderEditor::_dup_copy_nodes(int p_type, List<CopyItem> &r_items, Li } void VisualShaderEditor::_dup_paste_nodes(int p_type, List<CopyItem> &r_items, const List<VisualShader::Connection> &p_connections, const Vector2 &p_offset, bool p_duplicate) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if (p_duplicate) { undo_redo->create_action(TTR("Duplicate VisualShader Node(s)")); } else { + bool copy_buffer_empty = true; + for (const CopyItem &item : copy_items_buffer) { + if (!item.disabled) { + copy_buffer_empty = false; + break; + } + } + if (copy_buffer_empty) { + return; + } + undo_redo->create_action(TTR("Paste VisualShader Node(s)")); } @@ -3388,21 +4234,12 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, List<CopyItem> &r_items, c int base_id = visual_shader->get_valid_node_id(type); int id_from = base_id; - Map<int, int> connection_remap; - Set<int> unsupported_set; - Set<int> added_set; + HashMap<int, int> connection_remap; + HashSet<int> unsupported_set; + HashSet<int> added_set; for (CopyItem &item : r_items) { - bool unsupported = false; - for (int i = 0; i < add_options.size(); i++) { - if (add_options[i].type == item.node->get_class_name()) { - if (!_is_available(add_options[i].mode)) { - unsupported = true; - } - break; - } - } - if (unsupported) { + if (item.disabled) { unsupported_set.insert(item.id); continue; } @@ -3426,7 +4263,7 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, List<CopyItem> &r_items, c } undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, node, item.position + p_offset, id_from); - undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_from); + undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_from, false); added_set.insert(id_from); id_from++; @@ -3443,9 +4280,12 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, List<CopyItem> &r_items, c } id_from = base_id; - for (int i = 0; i < r_items.size(); i++) { + for (const CopyItem &item : r_items) { + if (item.disabled) { + continue; + } undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_from); - undo_redo->add_undo_method(graph_plugin.ptr(), "remove_node", type, id_from); + undo_redo->add_undo_method(graph_plugin.ptr(), "remove_node", type, id_from, false); id_from++; } @@ -3474,15 +4314,15 @@ void VisualShaderEditor::_duplicate_nodes() { int type = get_current_shader_type(); List<CopyItem> items; - List<VisualShader::Connection> connections; + List<VisualShader::Connection> node_connections; - _dup_copy_nodes(type, items, connections); + _dup_copy_nodes(type, items, node_connections); if (items.is_empty()) { return; } - _dup_paste_nodes(type, items, connections, Vector2(10, 10) * EDSCALE, true); + _dup_paste_nodes(type, items, node_connections, Vector2(10, 10) * EDSCALE, true); } void VisualShaderEditor::_copy_nodes(bool p_cut) { @@ -3491,6 +4331,7 @@ void VisualShaderEditor::_copy_nodes(bool p_cut) { _dup_copy_nodes(get_current_shader_type(), copy_items_buffer, copy_connections_buffer); if (p_cut) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Cut VisualShader Node(s)")); List<int> ids; @@ -3544,7 +4385,7 @@ void VisualShaderEditor::_mode_selected(int p_id) { } visual_shader->set_shader_type(VisualShader::Type(p_id + offset)); - _update_options_menu(); + _update_nodes(); _update_graph(); graph->grab_focus(); @@ -3572,87 +4413,182 @@ void VisualShaderEditor::_input_select_item(Ref<VisualShaderNodeInput> p_input, return; } - bool type_changed = p_input->get_input_type_by_name(p_name) != p_input->get_input_type_by_name(prev_name); + VisualShaderNode::PortType next_input_type = p_input->get_input_type_by_name(p_name); + VisualShaderNode::PortType prev_input_type = p_input->get_input_type_by_name(prev_name); + + bool type_changed = next_input_type != prev_input_type; + + EditorUndoRedoManager *undo_redo_man = EditorUndoRedoManager::get_singleton(); + undo_redo_man->create_action(TTR("Visual Shader Input Type Changed")); + + undo_redo_man->add_do_method(p_input.ptr(), "set_input_name", p_name); + undo_redo_man->add_undo_method(p_input.ptr(), "set_input_name", prev_name); + + if (type_changed) { + for (int type_id = 0; type_id < VisualShader::TYPE_MAX; type_id++) { + VisualShader::Type type = VisualShader::Type(type_id); + + int id = visual_shader->find_node_id(type, p_input); + if (id != VisualShader::NODE_ID_INVALID) { + bool is_expanded = p_input->is_output_port_expandable(0) && p_input->_is_output_port_expanded(0); + + int type_size = 0; + if (is_expanded) { + switch (next_input_type) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + type_size = 2; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + type_size = 3; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + type_size = 4; + } break; + default: + break; + } + } - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); - undo_redo->create_action(TTR("Visual Shader Input Type Changed")); + List<VisualShader::Connection> conns; + visual_shader->get_node_connections(type, &conns); + for (const VisualShader::Connection &E : conns) { + int cn_from_node = E.from_node; + int cn_from_port = E.from_port; + int cn_to_node = E.to_node; + int cn_to_port = E.to_port; + + if (cn_from_node == id) { + bool is_incompatible_types = !visual_shader->is_port_types_compatible(p_input->get_input_type_by_name(p_name), visual_shader->get_node(type, cn_to_node)->get_input_port_type(cn_to_port)); + + if (is_incompatible_types || cn_from_port > type_size) { + undo_redo_man->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo_man->add_undo_method(visual_shader.ptr(), "connect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo_man->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + undo_redo_man->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, cn_from_node, cn_from_port, cn_to_node, cn_to_port); + } + } + } - undo_redo->add_do_method(p_input.ptr(), "set_input_name", p_name); - undo_redo->add_undo_method(p_input.ptr(), "set_input_name", prev_name); + undo_redo_man->add_do_method(graph_plugin.ptr(), "update_node", type_id, id); + undo_redo_man->add_undo_method(graph_plugin.ptr(), "update_node", type_id, id); + } + } + } + + undo_redo_man->commit_action(); +} + +void VisualShaderEditor::_parameter_ref_select_item(Ref<VisualShaderNodeParameterRef> p_parameter_ref, String p_name) { + String prev_name = p_parameter_ref->get_parameter_name(); + + if (p_name == prev_name) { + return; + } + + bool type_changed = p_parameter_ref->get_parameter_type_by_name(p_name) != p_parameter_ref->get_parameter_type_by_name(prev_name); + + EditorUndoRedoManager *undo_redo_man = EditorUndoRedoManager::get_singleton(); + undo_redo_man->create_action(TTR("ParameterRef Name Changed")); + + undo_redo_man->add_do_method(p_parameter_ref.ptr(), "set_parameter_name", p_name); + undo_redo_man->add_undo_method(p_parameter_ref.ptr(), "set_parameter_name", prev_name); // update output port for (int type_id = 0; type_id < VisualShader::TYPE_MAX; type_id++) { VisualShader::Type type = VisualShader::Type(type_id); - int id = visual_shader->find_node_id(type, p_input); + int id = visual_shader->find_node_id(type, p_parameter_ref); if (id != VisualShader::NODE_ID_INVALID) { if (type_changed) { List<VisualShader::Connection> conns; visual_shader->get_node_connections(type, &conns); for (const VisualShader::Connection &E : conns) { if (E.from_node == id) { - if (visual_shader->is_port_types_compatible(p_input->get_input_type_by_name(p_name), visual_shader->get_node(type, E.to_node)->get_input_port_type(E.to_port))) { - undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + if (visual_shader->is_port_types_compatible(p_parameter_ref->get_parameter_type_by_name(p_name), visual_shader->get_node(type, E.to_node)->get_input_port_type(E.to_port))) { continue; } - undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); - undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo_man->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo_man->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo_man->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo_man->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); } } } - undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type_id, id); - undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type_id, id); + undo_redo_man->add_do_method(graph_plugin.ptr(), "update_node", type_id, id); + undo_redo_man->add_undo_method(graph_plugin.ptr(), "update_node", type_id, id); break; } } - undo_redo->commit_action(); + undo_redo_man->commit_action(); } -void VisualShaderEditor::_uniform_select_item(Ref<VisualShaderNodeUniformRef> p_uniform_ref, String p_name) { - String prev_name = p_uniform_ref->get_uniform_name(); +void VisualShaderEditor::_varying_select_item(Ref<VisualShaderNodeVarying> p_varying, String p_name) { + String prev_name = p_varying->get_varying_name(); if (p_name == prev_name) { return; } - bool type_changed = p_uniform_ref->get_uniform_type_by_name(p_name) != p_uniform_ref->get_uniform_type_by_name(prev_name); + bool is_getter = Ref<VisualShaderNodeVaryingGetter>(p_varying.ptr()).is_valid(); - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); - undo_redo->create_action(TTR("UniformRef Name Changed")); + EditorUndoRedoManager *undo_redo_man = EditorUndoRedoManager::get_singleton(); + undo_redo_man->create_action(TTR("Varying Name Changed")); - undo_redo->add_do_method(p_uniform_ref.ptr(), "set_uniform_name", p_name); - undo_redo->add_undo_method(p_uniform_ref.ptr(), "set_uniform_name", prev_name); + undo_redo_man->add_do_method(p_varying.ptr(), "set_varying_name", p_name); + undo_redo_man->add_undo_method(p_varying.ptr(), "set_varying_name", prev_name); - // update output port + VisualShader::VaryingType vtype = p_varying->get_varying_type_by_name(p_name); + VisualShader::VaryingType prev_vtype = p_varying->get_varying_type_by_name(prev_name); + + bool type_changed = vtype != prev_vtype; + + if (type_changed) { + undo_redo_man->add_do_method(p_varying.ptr(), "set_varying_type", vtype); + undo_redo_man->add_undo_method(p_varying.ptr(), "set_varying_type", prev_vtype); + } + + // update ports for (int type_id = 0; type_id < VisualShader::TYPE_MAX; type_id++) { VisualShader::Type type = VisualShader::Type(type_id); - int id = visual_shader->find_node_id(type, p_uniform_ref); + int id = visual_shader->find_node_id(type, p_varying); + if (id != VisualShader::NODE_ID_INVALID) { if (type_changed) { List<VisualShader::Connection> conns; visual_shader->get_node_connections(type, &conns); + for (const VisualShader::Connection &E : conns) { - if (E.from_node == id) { - if (visual_shader->is_port_types_compatible(p_uniform_ref->get_uniform_type_by_name(p_name), visual_shader->get_node(type, E.to_node)->get_input_port_type(E.to_port))) { - continue; + if (is_getter) { + if (E.from_node == id) { + if (visual_shader->is_port_types_compatible(p_varying->get_varying_type_by_name(p_name), visual_shader->get_node(type, E.to_node)->get_input_port_type(E.to_port))) { + continue; + } + undo_redo_man->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo_man->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo_man->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo_man->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + } + } else { + if (E.to_node == id) { + if (visual_shader->is_port_types_compatible(p_varying->get_varying_type_by_name(p_name), visual_shader->get_node(type, E.from_node)->get_output_port_type(E.from_port))) { + continue; + } + undo_redo_man->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo_man->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo_man->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo_man->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); } - undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); - undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); } } } - undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type_id, id); - undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type_id, id); + + undo_redo_man->add_do_method(graph_plugin.ptr(), "update_node", type_id, id); + undo_redo_man->add_undo_method(graph_plugin.ptr(), "update_node", type_id, id); break; } } - undo_redo->commit_action(); + undo_redo_man->commit_action(); } void VisualShaderEditor::_float_constant_selected(int p_which) { @@ -3666,6 +4602,7 @@ void VisualShaderEditor::_float_constant_selected(int p_which) { return; // same } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(vformat(TTR("Set Constant: %s"), float_constant_defs[p_which].name)); undo_redo->add_do_method(node.ptr(), "set_constant", float_constant_defs[p_which].value); undo_redo->add_undo_method(node.ptr(), "set_constant", node->get_constant()); @@ -3697,7 +4634,7 @@ void VisualShaderEditor::_member_create() { TreeItem *item = members->get_selected(); if (item != nullptr && item->has_meta("id")) { int idx = members->get_selected()->get_meta("id"); - _add_node(idx, add_options[idx].sub_func); + _add_node(idx, add_options[idx].ops); members_dialog->hide(); } } @@ -3709,6 +4646,101 @@ void VisualShaderEditor::_member_cancel() { from_slot = -1; } +void VisualShaderEditor::_update_varying_tree() { + varyings->clear(); + TreeItem *root = varyings->create_item(); + + int count = visual_shader->get_varyings_count(); + + for (int i = 0; i < count; i++) { + const VisualShader::Varying *varying = visual_shader->get_varying_by_index(i); + + if (varying) { + TreeItem *item = varyings->create_item(root); + item->set_text(0, varying->name); + + if (i == 0) { + item->select(0); + } + + switch (varying->type) { + case VisualShader::VARYING_TYPE_FLOAT: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("float"), SNAME("EditorIcons"))); + break; + case VisualShader::VARYING_TYPE_INT: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("int"), SNAME("EditorIcons"))); + break; + case VisualShader::VARYING_TYPE_UINT: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("uint"), SNAME("EditorIcons"))); + break; + case VisualShader::VARYING_TYPE_VECTOR_2D: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons"))); + break; + case VisualShader::VARYING_TYPE_VECTOR_3D: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons"))); + break; + case VisualShader::VARYING_TYPE_VECTOR_4D: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector4"), SNAME("EditorIcons"))); + break; + case VisualShader::VARYING_TYPE_BOOLEAN: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("bool"), SNAME("EditorIcons"))); + break; + case VisualShader::VARYING_TYPE_TRANSFORM: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons"))); + break; + default: + break; + } + } + } + + varying_button->get_popup()->set_item_disabled(int(VaryingMenuOptions::REMOVE), count == 0); +} + +void VisualShaderEditor::_varying_create() { + _add_varying(varying_name->get_text(), (VisualShader::VaryingMode)varying_mode->get_selected(), (VisualShader::VaryingType)varying_type->get_selected()); + add_varying_dialog->hide(); +} + +void VisualShaderEditor::_varying_name_changed(const String &p_text) { + String name = p_text; + + if (!name.is_valid_identifier()) { + varying_error_label->show(); + varying_error_label->set_text(TTR("Invalid name for varying.")); + add_varying_dialog->get_ok_button()->set_disabled(true); + return; + } + if (visual_shader->has_varying(name)) { + varying_error_label->show(); + varying_error_label->set_text(TTR("Varying with that name is already exist.")); + add_varying_dialog->get_ok_button()->set_disabled(true); + return; + } + if (varying_error_label->is_visible()) { + varying_error_label->hide(); + add_varying_dialog->set_size(Size2(add_varying_dialog->get_size().x, 0)); + } + add_varying_dialog->get_ok_button()->set_disabled(false); +} + +void VisualShaderEditor::_varying_deleted() { + TreeItem *item = varyings->get_selected(); + + if (item != nullptr) { + _remove_varying(item->get_text(0)); + remove_varying_dialog->hide(); + } +} + +void VisualShaderEditor::_varying_selected() { + add_varying_dialog->get_ok_button()->set_disabled(false); +} + +void VisualShaderEditor::_varying_unselected() { + add_varying_dialog->get_ok_button()->set_disabled(true); +} + void VisualShaderEditor::_tools_menu_option(int p_idx) { TreeItem *category = members->get_root()->get_first_child(); @@ -3760,7 +4792,7 @@ void VisualShaderEditor::_node_menu_id_pressed(int p_idx) { _paste_nodes(true, menu_point); break; case NodeMenuOptions::DELETE: - _delete_nodes_request(); + _delete_nodes_request(TypedArray<StringName>()); break; case NodeMenuOptions::DUPLICATE: _duplicate_nodes(); @@ -3768,11 +4800,11 @@ void VisualShaderEditor::_node_menu_id_pressed(int p_idx) { case NodeMenuOptions::CLEAR_COPY_BUFFER: _clear_copy_buffer(); break; - case NodeMenuOptions::CONVERT_CONSTANTS_TO_UNIFORMS: - _convert_constants_to_uniforms(false); + case NodeMenuOptions::CONVERT_CONSTANTS_TO_PARAMETERS: + _convert_constants_to_parameters(false); break; - case NodeMenuOptions::CONVERT_UNIFORMS_TO_CONSTANTS: - _convert_constants_to_uniforms(true); + case NodeMenuOptions::CONVERT_PARAMETERS_TO_CONSTANTS: + _convert_constants_to_parameters(true); break; case NodeMenuOptions::SET_COMMENT_TITLE: _comment_title_popup_show(get_screen_position() + get_local_mouse_position(), selected_comment); @@ -3800,11 +4832,6 @@ Variant VisualShaderEditor::get_drag_data_fw(const Point2 &p_point, Control *p_f Dictionary d; d["id"] = id; - if (op.sub_func == -1) { - d["sub_func"] = op.sub_func_str; - } else { - d["sub_func"] = op.sub_func; - } Label *label = memnew(Label); label->set_text(it->get_text(0)); @@ -3837,8 +4864,9 @@ void VisualShaderEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da int idx = d["id"]; saved_node_pos = p_point; saved_node_pos_dirty = true; - _add_node(idx, add_options[idx].sub_func); + _add_node(idx, add_options[idx].ops); } else if (d.has("files")) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Node(s) to Visual Shader")); if (d["files"].get_type() == Variant::PACKED_STRING_ARRAY) { @@ -3846,8 +4874,8 @@ void VisualShaderEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da for (int i = 0; i < arr.size(); i++) { String type = ResourceLoader::get_resource_type(arr[i]); if (type == "GDScript") { - Ref<Script> script = ResourceLoader::load(arr[i]); - if (script->get_instance_base_type() == "VisualShaderNodeCustom") { + Ref<Script> scr = ResourceLoader::load(arr[i]); + if (scr->get_instance_base_type() == "VisualShaderNodeCustom") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; @@ -3862,33 +4890,33 @@ void VisualShaderEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } } if (idx != -1) { - _add_node(idx, -1, arr[i], i); + _add_node(idx, {}, arr[i], i); } } } else if (type == "CurveTexture") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_node(curve_node_option_idx, -1, arr[i], i); + _add_node(curve_node_option_idx, {}, arr[i], i); } else if (type == "CurveXYZTexture") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_node(curve_xyz_node_option_idx, -1, arr[i], i); + _add_node(curve_xyz_node_option_idx, {}, arr[i], i); } else if (ClassDB::get_parent_class(type) == "Texture2D") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_node(texture2d_node_option_idx, -1, arr[i], i); + _add_node(texture2d_node_option_idx, {}, arr[i], i); } else if (type == "Texture2DArray") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_node(texture2d_array_node_option_idx, -1, arr[i], i); + _add_node(texture2d_array_node_option_idx, {}, arr[i], i); } else if (ClassDB::get_parent_class(type) == "Texture3D") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_node(texture3d_node_option_idx, -1, arr[i], i); + _add_node(texture3d_node_option_idx, {}, arr[i], i); } else if (type == "Cubemap") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_node(cubemap_node_option_idx, -1, arr[i], i); + _add_node(cubemap_node_option_idx, {}, arr[i], i); } } } @@ -3928,9 +4956,9 @@ void VisualShaderEditor::_preview_size_changed() { preview_vbox->set_custom_minimum_size(preview_window->get_size()); } -static ShaderLanguage::DataType _get_global_variable_type(const StringName &p_variable) { - RS::GlobalVariableType gvt = RS::get_singleton()->global_variable_get_type(p_variable); - return RS::global_variable_type_get_shader_datatype(gvt); +static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) { + RS::GlobalShaderParameterType gvt = RS::get_singleton()->global_shader_parameter_get_type(p_variable); + return (ShaderLanguage::DataType)RS::global_shader_uniform_type_get_shader_datatype(gvt); } void VisualShaderEditor::_update_preview() { @@ -3947,7 +4975,7 @@ void VisualShaderEditor::_update_preview() { info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(visual_shader->get_mode())); info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(visual_shader->get_mode())); info.shader_types = ShaderTypes::get_singleton()->get_types(); - info.global_variable_type_func = _get_global_variable_type; + info.global_shader_uniform_type_func = _get_global_shader_uniform_type; ShaderLanguage sl; @@ -3970,6 +4998,29 @@ void VisualShaderEditor::_update_preview() { } } +void VisualShaderEditor::_update_next_previews(int p_node_id) { + VisualShader::Type type = get_current_shader_type(); + + LocalVector<int> nodes; + _get_next_nodes_recursively(type, p_node_id, nodes); + + for (int node_id : nodes) { + if (graph_plugin->is_preview_visible(node_id)) { + graph_plugin->update_node_deferred(type, node_id); + } + } +} + +void VisualShaderEditor::_get_next_nodes_recursively(VisualShader::Type p_type, int p_node_id, LocalVector<int> &r_nodes) const { + LocalVector<int> next_connections; + visual_shader->get_next_connected_nodes(p_type, p_node_id, next_connections); + + for (int node_id : next_connections) { + r_nodes.push_back(node_id); + _get_next_nodes_recursively(p_type, node_id, r_nodes); + } +} + void VisualShaderEditor::_visibility_changed() { if (!is_visible()) { if (preview_window->is_visible()) { @@ -3981,69 +5032,67 @@ void VisualShaderEditor::_visibility_changed() { } void VisualShaderEditor::_bind_methods() { + ClassDB::bind_method("_update_nodes", &VisualShaderEditor::_update_nodes); ClassDB::bind_method("_update_graph", &VisualShaderEditor::_update_graph); - ClassDB::bind_method("_update_options_menu", &VisualShaderEditor::_update_options_menu); ClassDB::bind_method("_add_node", &VisualShaderEditor::_add_node); ClassDB::bind_method("_node_changed", &VisualShaderEditor::_node_changed); ClassDB::bind_method("_input_select_item", &VisualShaderEditor::_input_select_item); - ClassDB::bind_method("_uniform_select_item", &VisualShaderEditor::_uniform_select_item); + ClassDB::bind_method("_parameter_ref_select_item", &VisualShaderEditor::_parameter_ref_select_item); + ClassDB::bind_method("_varying_select_item", &VisualShaderEditor::_varying_select_item); ClassDB::bind_method("_set_node_size", &VisualShaderEditor::_set_node_size); ClassDB::bind_method("_clear_copy_buffer", &VisualShaderEditor::_clear_copy_buffer); - ClassDB::bind_method("_update_uniforms", &VisualShaderEditor::_update_uniforms); + ClassDB::bind_method("_update_parameters", &VisualShaderEditor::_update_parameters); + ClassDB::bind_method("_update_varyings", &VisualShaderEditor::_update_varyings); + ClassDB::bind_method("_update_varying_tree", &VisualShaderEditor::_update_varying_tree); ClassDB::bind_method("_set_mode", &VisualShaderEditor::_set_mode); ClassDB::bind_method("_nodes_dragged", &VisualShaderEditor::_nodes_dragged); ClassDB::bind_method("_float_constant_selected", &VisualShaderEditor::_float_constant_selected); ClassDB::bind_method("_update_constant", &VisualShaderEditor::_update_constant); - ClassDB::bind_method("_update_uniform", &VisualShaderEditor::_update_uniform); + ClassDB::bind_method("_update_parameter", &VisualShaderEditor::_update_parameter); ClassDB::bind_method("_expand_output_port", &VisualShaderEditor::_expand_output_port); - - ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &VisualShaderEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &VisualShaderEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("_drop_data_fw"), &VisualShaderEditor::drop_data_fw); + ClassDB::bind_method("_update_options_menu_deferred", &VisualShaderEditor::_update_options_menu_deferred); + ClassDB::bind_method("_rebuild_shader_deferred", &VisualShaderEditor::_rebuild_shader_deferred); + ClassDB::bind_method("_resources_removed", &VisualShaderEditor::_resources_removed); + ClassDB::bind_method("_update_next_previews", &VisualShaderEditor::_update_next_previews); ClassDB::bind_method("_is_available", &VisualShaderEditor::_is_available); } -VisualShaderEditor *VisualShaderEditor::singleton = nullptr; - VisualShaderEditor::VisualShaderEditor() { - singleton = this; - updating = false; - saved_node_pos_dirty = false; - saved_node_pos = Point2(0, 0); ShaderLanguage::get_keyword_list(&keyword_list); - - pending_update_preview = false; - shader_error = false; - - to_node = -1; - to_slot = -1; - from_node = -1; - from_slot = -1; + EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &VisualShaderEditor::_resource_saved)); + FileSystemDock::get_singleton()->get_script_create_dialog()->connect("script_created", callable_mp(this, &VisualShaderEditor::_script_created)); + FileSystemDock::get_singleton()->connect("resource_removed", callable_mp(this, &VisualShaderEditor::_resource_removed)); graph = memnew(GraphEdit); graph->get_zoom_hbox()->set_h_size_flags(SIZE_EXPAND_FILL); graph->set_v_size_flags(SIZE_EXPAND_FILL); graph->set_h_size_flags(SIZE_EXPAND_FILL); + graph->set_show_zoom_label(true); add_child(graph); - graph->set_drag_forwarding(this); - float graph_minimap_opacity = EditorSettings::get_singleton()->get("editors/visual_editors/minimap_opacity"); + SET_DRAG_FORWARDING_GCD(graph, VisualShaderEditor); + float graph_minimap_opacity = EDITOR_GET("editors/visual_editors/minimap_opacity"); graph->set_minimap_opacity(graph_minimap_opacity); + float graph_lines_curvature = EDITOR_GET("editors/visual_editors/lines_curvature"); + graph->set_connection_lines_curvature(graph_lines_curvature); graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_SCALAR); graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_SCALAR_INT); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_SCALAR_UINT); graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_BOOLEAN); - graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_VECTOR_4D); graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_TRANSFORM); graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_SAMPLER); //graph->add_valid_left_disconnect_type(0); graph->set_v_size_flags(SIZE_EXPAND_FILL); - graph->connect("connection_request", callable_mp(this, &VisualShaderEditor::_connection_request), varray(), CONNECT_DEFERRED); - graph->connect("disconnection_request", callable_mp(this, &VisualShaderEditor::_disconnection_request), varray(), CONNECT_DEFERRED); + graph->connect("connection_request", callable_mp(this, &VisualShaderEditor::_connection_request), CONNECT_DEFERRED); + graph->connect("disconnection_request", callable_mp(this, &VisualShaderEditor::_disconnection_request), CONNECT_DEFERRED); graph->connect("node_selected", callable_mp(this, &VisualShaderEditor::_node_selected)); graph->connect("scroll_offset_changed", callable_mp(this, &VisualShaderEditor::_scroll_changed)); graph->connect("duplicate_nodes_request", callable_mp(this, &VisualShaderEditor::_duplicate_nodes)); - graph->connect("copy_nodes_request", callable_mp(this, &VisualShaderEditor::_copy_nodes), varray(false)); - graph->connect("paste_nodes_request", callable_mp(this, &VisualShaderEditor::_paste_nodes), varray(false, Point2())); + graph->connect("copy_nodes_request", callable_mp(this, &VisualShaderEditor::_copy_nodes).bind(false)); + graph->connect("paste_nodes_request", callable_mp(this, &VisualShaderEditor::_paste_nodes).bind(false, Point2())); graph->connect("delete_nodes_request", callable_mp(this, &VisualShaderEditor::_delete_nodes_request)); graph->connect("gui_input", callable_mp(this, &VisualShaderEditor::_graph_gui_input)); graph->connect("connection_to_empty", callable_mp(this, &VisualShaderEditor::_connection_to_empty)); @@ -4051,20 +5100,60 @@ VisualShaderEditor::VisualShaderEditor() { graph->connect("visibility_changed", callable_mp(this, &VisualShaderEditor::_visibility_changed)); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR_INT); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR_UINT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR_4D); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_BOOLEAN); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_SCALAR_INT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_SCALAR); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_SCALAR_INT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_SCALAR_UINT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_VECTOR_4D); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_BOOLEAN); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_SCALAR); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_SCALAR_INT); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_BOOLEAN); + + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_UINT, VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_UINT, VisualShaderNode::PORT_TYPE_SCALAR_INT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_UINT, VisualShaderNode::PORT_TYPE_SCALAR_UINT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_UINT, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_UINT, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_UINT, VisualShaderNode::PORT_TYPE_VECTOR_4D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_UINT, VisualShaderNode::PORT_TYPE_BOOLEAN); + + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_SCALAR_INT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_SCALAR_UINT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_VECTOR_4D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_BOOLEAN); + + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_SCALAR_INT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_SCALAR_UINT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_VECTOR_4D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_BOOLEAN); + + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_SCALAR_INT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_SCALAR_UINT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_VECTOR_4D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_BOOLEAN); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_SCALAR); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_SCALAR_INT); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_SCALAR_UINT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_VECTOR_4D); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_BOOLEAN); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_TRANSFORM, VisualShaderNode::PORT_TYPE_TRANSFORM); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SAMPLER, VisualShaderNode::PORT_TYPE_SAMPLER); @@ -4117,15 +5206,25 @@ VisualShaderEditor::VisualShaderEditor() { add_node = memnew(Button); add_node->set_flat(true); - graph->get_zoom_hbox()->add_child(add_node); add_node->set_text(TTR("Add Node...")); + graph->get_zoom_hbox()->add_child(add_node); graph->get_zoom_hbox()->move_child(add_node, 0); - add_node->connect("pressed", callable_mp(this, &VisualShaderEditor::_show_members_dialog), varray(false, VisualShaderNode::PORT_TYPE_MAX, VisualShaderNode::PORT_TYPE_MAX)); + add_node->connect("pressed", callable_mp(this, &VisualShaderEditor::_show_members_dialog).bind(false, VisualShaderNode::PORT_TYPE_MAX, VisualShaderNode::PORT_TYPE_MAX)); + + varying_button = memnew(MenuButton); + varying_button->set_text(TTR("Manage Varyings")); + varying_button->set_switch_on_hover(true); + graph->get_zoom_hbox()->add_child(varying_button); + + PopupMenu *varying_menu = varying_button->get_popup(); + varying_menu->add_item(TTR("Add Varying"), int(VaryingMenuOptions::ADD)); + varying_menu->add_item(TTR("Remove Varying"), int(VaryingMenuOptions::REMOVE)); + varying_menu->connect("id_pressed", callable_mp(this, &VisualShaderEditor::_varying_menu_id_pressed)); preview_shader = memnew(Button); preview_shader->set_flat(true); preview_shader->set_toggle_mode(true); - preview_shader->set_tooltip(TTR("Show generated shader code.")); + preview_shader->set_tooltip_text(TTR("Show generated shader code.")); graph->get_zoom_hbox()->add_child(preview_shader); preview_shader->connect("pressed", callable_mp(this, &VisualShaderEditor::_show_preview_text)); @@ -4134,7 +5233,7 @@ VisualShaderEditor::VisualShaderEditor() { /////////////////////////////////////// preview_window = memnew(Window); - preview_window->set_title(TTR("Generated shader code")); + preview_window->set_title(TTR("Generated Shader Code")); preview_window->set_visible(preview_showed); preview_window->connect("close_requested", callable_mp(this, &VisualShaderEditor::_preview_close_requested)); preview_window->connect("size_changed", callable_mp(this, &VisualShaderEditor::_preview_size_changed)); @@ -4158,7 +5257,7 @@ VisualShaderEditor::VisualShaderEditor() { error_label = memnew(Label); error_panel->add_child(error_label); - error_label->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART); + error_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); /////////////////////////////////////// // POPUP MENU @@ -4195,14 +5294,14 @@ VisualShaderEditor::VisualShaderEditor() { tools = memnew(MenuButton); filter_hb->add_child(tools); - tools->set_tooltip(TTR("Options")); + tools->set_tooltip_text(TTR("Options")); tools->get_popup()->connect("id_pressed", callable_mp(this, &VisualShaderEditor::_tools_menu_option)); tools->get_popup()->add_item(TTR("Expand All"), EXPAND_ALL); tools->get_popup()->add_item(TTR("Collapse All"), COLLAPSE_ALL); members = memnew(Tree); members_vb->add_child(members); - members->set_drag_forwarding(this); + SET_DRAG_FORWARDING_GCD(members, VisualShaderEditor); members->set_h_size_flags(SIZE_EXPAND_FILL); members->set_v_size_flags(SIZE_EXPAND_FILL); members->set_hide_root(true); @@ -4227,7 +5326,7 @@ VisualShaderEditor::VisualShaderEditor() { highend_label->set_visible(false); highend_label->set_text("Vulkan"); highend_label->set_mouse_filter(Control::MOUSE_FILTER_STOP); - highend_label->set_tooltip(TTR("High-end node")); + highend_label->set_tooltip_text(TTR("High-end node")); node_desc = memnew(RichTextLabel); members_vb->add_child(node_desc); @@ -4239,14 +5338,85 @@ VisualShaderEditor::VisualShaderEditor() { members_dialog->set_title(TTR("Create Shader Node")); members_dialog->set_exclusive(false); members_dialog->add_child(members_vb); - members_dialog->get_ok_button()->set_text(TTR("Create")); + members_dialog->set_ok_button_text(TTR("Create")); members_dialog->get_ok_button()->connect("pressed", callable_mp(this, &VisualShaderEditor::_member_create)); members_dialog->get_ok_button()->set_disabled(true); - members_dialog->connect("cancelled", callable_mp(this, &VisualShaderEditor::_member_cancel)); + members_dialog->connect("canceled", callable_mp(this, &VisualShaderEditor::_member_cancel)); add_child(members_dialog); + // add varyings dialog + { + add_varying_dialog = memnew(ConfirmationDialog); + add_varying_dialog->set_title(TTR("Create Shader Varying")); + add_varying_dialog->set_exclusive(false); + add_varying_dialog->set_ok_button_text(TTR("Create")); + add_varying_dialog->get_ok_button()->connect("pressed", callable_mp(this, &VisualShaderEditor::_varying_create)); + add_varying_dialog->get_ok_button()->set_disabled(true); + add_child(add_varying_dialog); + + VBoxContainer *vb = memnew(VBoxContainer); + add_varying_dialog->add_child(vb); + + HBoxContainer *hb = memnew(HBoxContainer); + vb->add_child(hb); + hb->set_h_size_flags(SIZE_EXPAND_FILL); + + varying_type = memnew(OptionButton); + hb->add_child(varying_type); + varying_type->add_item("Float"); + varying_type->add_item("Int"); + varying_type->add_item("UInt"); + varying_type->add_item("Vector2"); + varying_type->add_item("Vector3"); + varying_type->add_item("Vector4"); + varying_type->add_item("Boolean"); + varying_type->add_item("Transform"); + + varying_name = memnew(LineEdit); + hb->add_child(varying_name); + varying_name->set_custom_minimum_size(Size2(150 * EDSCALE, 0)); + varying_name->set_h_size_flags(SIZE_EXPAND_FILL); + varying_name->connect("text_changed", callable_mp(this, &VisualShaderEditor::_varying_name_changed)); + + varying_mode = memnew(OptionButton); + hb->add_child(varying_mode); + varying_mode->add_item("Vertex -> [Fragment, Light]"); + varying_mode->add_item("Fragment -> Light"); + + varying_error_label = memnew(Label); + vb->add_child(varying_error_label); + varying_error_label->set_h_size_flags(SIZE_EXPAND_FILL); + + varying_error_label->hide(); + } + + // remove varying dialog + { + remove_varying_dialog = memnew(ConfirmationDialog); + remove_varying_dialog->set_title(TTR("Delete Shader Varying")); + remove_varying_dialog->set_exclusive(false); + remove_varying_dialog->set_ok_button_text(TTR("Delete")); + remove_varying_dialog->get_ok_button()->connect("pressed", callable_mp(this, &VisualShaderEditor::_varying_deleted)); + add_child(remove_varying_dialog); + + VBoxContainer *vb = memnew(VBoxContainer); + remove_varying_dialog->add_child(vb); + + varyings = memnew(Tree); + vb->add_child(varyings); + varyings->set_h_size_flags(SIZE_EXPAND_FILL); + varyings->set_v_size_flags(SIZE_EXPAND_FILL); + varyings->set_hide_root(true); + varyings->set_allow_reselect(true); + varyings->set_hide_folding(false); + varyings->set_custom_minimum_size(Size2(180 * EDSCALE, 200 * EDSCALE)); + varyings->connect("item_activated", callable_mp(this, &VisualShaderEditor::_varying_deleted)); + varyings->connect("item_selected", callable_mp(this, &VisualShaderEditor::_varying_selected)); + varyings->connect("nothing_selected", callable_mp(this, &VisualShaderEditor::_varying_unselected)); + } + alert = memnew(AcceptDialog); - alert->get_label()->set_autowrap_mode(Label::AUTOWRAP_WORD); + alert->get_label()->set_autowrap_mode(TextServer::AUTOWRAP_WORD); alert->get_label()->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); alert->get_label()->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); alert->get_label()->set_custom_minimum_size(Size2(400, 60) * EDSCALE); @@ -4287,576 +5457,792 @@ VisualShaderEditor::VisualShaderEditor() { // COLOR - add_options.push_back(AddOption("ColorFunc", "Color", "Common", "VisualShaderNodeColorFunc", TTR("Color function."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ColorOp", "Color", "Common", "VisualShaderNodeColorOp", TTR("Color operator."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("ColorFunc", "Color/Common", "VisualShaderNodeColorFunc", TTR("Color function."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ColorOp", "Color/Common", "VisualShaderNodeColorOp", TTR("Color operator."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + + add_options.push_back(AddOption("Grayscale", "Color/Functions", "VisualShaderNodeColorFunc", TTR("Grayscale function."), { VisualShaderNodeColorFunc::FUNC_GRAYSCALE }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("HSV2RGB", "Color/Functions", "VisualShaderNodeColorFunc", TTR("Converts HSV vector to RGB equivalent."), { VisualShaderNodeColorFunc::FUNC_HSV2RGB, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("RGB2HSV", "Color/Functions", "VisualShaderNodeColorFunc", TTR("Converts RGB vector to HSV equivalent."), { VisualShaderNodeColorFunc::FUNC_RGB2HSV, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Sepia", "Color/Functions", "VisualShaderNodeColorFunc", TTR("Sepia function."), { VisualShaderNodeColorFunc::FUNC_SEPIA }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); - add_options.push_back(AddOption("Grayscale", "Color", "Functions", "VisualShaderNodeColorFunc", TTR("Grayscale function."), VisualShaderNodeColorFunc::FUNC_GRAYSCALE, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("HSV2RGB", "Color", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts HSV vector to RGB equivalent."), VisualShaderNodeVectorFunc::FUNC_HSV2RGB, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("RGB2HSV", "Color", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts RGB vector to HSV equivalent."), VisualShaderNodeVectorFunc::FUNC_RGB2HSV, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Sepia", "Color", "Functions", "VisualShaderNodeColorFunc", TTR("Sepia function."), VisualShaderNodeColorFunc::FUNC_SEPIA, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("Burn", "Color/Operators", "VisualShaderNodeColorOp", TTR("Burn operator."), { VisualShaderNodeColorOp::OP_BURN }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Darken", "Color/Operators", "VisualShaderNodeColorOp", TTR("Darken operator."), { VisualShaderNodeColorOp::OP_DARKEN }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Difference", "Color/Operators", "VisualShaderNodeColorOp", TTR("Difference operator."), { VisualShaderNodeColorOp::OP_DIFFERENCE }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Dodge", "Color/Operators", "VisualShaderNodeColorOp", TTR("Dodge operator."), { VisualShaderNodeColorOp::OP_DODGE }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("HardLight", "Color/Operators", "VisualShaderNodeColorOp", TTR("HardLight operator."), { VisualShaderNodeColorOp::OP_HARD_LIGHT }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Lighten", "Color/Operators", "VisualShaderNodeColorOp", TTR("Lighten operator."), { VisualShaderNodeColorOp::OP_LIGHTEN }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Overlay", "Color/Operators", "VisualShaderNodeColorOp", TTR("Overlay operator."), { VisualShaderNodeColorOp::OP_OVERLAY }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Screen", "Color/Operators", "VisualShaderNodeColorOp", TTR("Screen operator."), { VisualShaderNodeColorOp::OP_SCREEN }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("SoftLight", "Color/Operators", "VisualShaderNodeColorOp", TTR("SoftLight operator."), { VisualShaderNodeColorOp::OP_SOFT_LIGHT }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); - add_options.push_back(AddOption("Burn", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Burn operator."), VisualShaderNodeColorOp::OP_BURN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Darken", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Darken operator."), VisualShaderNodeColorOp::OP_DARKEN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Difference", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Difference operator."), VisualShaderNodeColorOp::OP_DIFFERENCE, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Dodge", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Dodge operator."), VisualShaderNodeColorOp::OP_DODGE, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("HardLight", "Color", "Operators", "VisualShaderNodeColorOp", TTR("HardLight operator."), VisualShaderNodeColorOp::OP_HARD_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Lighten", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Lighten operator."), VisualShaderNodeColorOp::OP_LIGHTEN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Overlay", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Overlay operator."), VisualShaderNodeColorOp::OP_OVERLAY, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Screen", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Screen operator."), VisualShaderNodeColorOp::OP_SCREEN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("SoftLight", "Color", "Operators", "VisualShaderNodeColorOp", TTR("SoftLight operator."), VisualShaderNodeColorOp::OP_SOFT_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("ColorConstant", "Color/Variables", "VisualShaderNodeColorConstant", TTR("Color constant."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ColorParameter", "Color/Variables", "VisualShaderNodeColorParameter", TTR("Color parameter."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); - add_options.push_back(AddOption("ColorConstant", "Color", "Variables", "VisualShaderNodeColorConstant", TTR("Color constant."), -1, -1)); - add_options.push_back(AddOption("ColorUniform", "Color", "Variables", "VisualShaderNodeColorUniform", TTR("Color uniform."), -1, -1)); + // COMMON + + add_options.push_back(AddOption("DerivativeFunc", "Common", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) Derivative function."), {}, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); // CONDITIONAL const String &compare_func_desc = TTR("Returns the boolean result of the %s comparison between two parameters."); - add_options.push_back(AddOption("Equal", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Equal (==)")), VisualShaderNodeCompare::FUNC_EQUAL, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("GreaterThan", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Greater Than (>)")), VisualShaderNodeCompare::FUNC_GREATER_THAN, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("GreaterThanEqual", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Greater Than or Equal (>=)")), VisualShaderNodeCompare::FUNC_GREATER_THAN_EQUAL, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("If", "Conditional", "Functions", "VisualShaderNodeIf", TTR("Returns an associated vector if the provided scalars are equal, greater or less."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("IsInf", "Conditional", "Functions", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between INF and a scalar parameter."), VisualShaderNodeIs::FUNC_IS_INF, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("IsNaN", "Conditional", "Functions", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between NaN and a scalar parameter."), VisualShaderNodeIs::FUNC_IS_NAN, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("LessThan", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Less Than (<)")), VisualShaderNodeCompare::FUNC_LESS_THAN, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("LessThanEqual", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Less Than or Equal (<=)")), VisualShaderNodeCompare::FUNC_LESS_THAN_EQUAL, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("NotEqual", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Not Equal (!=)")), VisualShaderNodeCompare::FUNC_NOT_EQUAL, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("Switch", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated vector if the provided boolean value is true or false."), VisualShaderNodeSwitch::OP_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("SwitchBool", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated boolean if the provided boolean value is true or false."), VisualShaderNodeSwitch::OP_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("SwitchFloat", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated floating-point scalar if the provided boolean value is true or false."), VisualShaderNodeSwitch::OP_TYPE_FLOAT, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("SwitchInt", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated integer scalar if the provided boolean value is true or false."), VisualShaderNodeSwitch::OP_TYPE_INT, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("SwitchTransform", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated transform if the provided boolean value is true or false."), VisualShaderNodeSwitch::OP_TYPE_TRANSFORM, VisualShaderNode::PORT_TYPE_TRANSFORM)); - - add_options.push_back(AddOption("Compare", "Conditional", "Common", "VisualShaderNodeCompare", TTR("Returns the boolean result of the comparison between two parameters."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("Is", "Conditional", "Common", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between INF (or NaN) and a scalar parameter."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); - - add_options.push_back(AddOption("BooleanConstant", "Conditional", "Variables", "VisualShaderNodeBooleanConstant", TTR("Boolean constant."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("BooleanUniform", "Conditional", "Variables", "VisualShaderNodeBooleanUniform", TTR("Boolean uniform."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("Equal (==)", "Conditional/Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Equal (==)")), { VisualShaderNodeCompare::FUNC_EQUAL }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("GreaterThan (>)", "Conditional/Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Greater Than (>)")), { VisualShaderNodeCompare::FUNC_GREATER_THAN }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("GreaterThanEqual (>=)", "Conditional/Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Greater Than or Equal (>=)")), { VisualShaderNodeCompare::FUNC_GREATER_THAN_EQUAL }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("If", "Conditional/Functions", "VisualShaderNodeIf", TTR("Returns an associated vector if the provided scalars are equal, greater or less."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("IsInf", "Conditional/Functions", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between INF and a scalar parameter."), { VisualShaderNodeIs::FUNC_IS_INF }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("IsNaN", "Conditional/Functions", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between NaN and a scalar parameter."), { VisualShaderNodeIs::FUNC_IS_NAN }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("LessThan (<)", "Conditional/Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Less Than (<)")), { VisualShaderNodeCompare::FUNC_LESS_THAN }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("LessThanEqual (<=)", "Conditional/Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Less Than or Equal (<=)")), { VisualShaderNodeCompare::FUNC_LESS_THAN_EQUAL }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("NotEqual (!=)", "Conditional/Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Not Equal (!=)")), { VisualShaderNodeCompare::FUNC_NOT_EQUAL }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("Switch (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated 3D vector if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Switch2D (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated 2D vector if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("SwitchBool (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated boolean if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_BOOLEAN }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("SwitchFloat (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated floating-point scalar if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_FLOAT }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("SwitchInt (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated integer scalar if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_INT }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("SwitchTransform (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated transform if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_TRANSFORM }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("SwitchUInt (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated unsigned integer scalar if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_UINT }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + + add_options.push_back(AddOption("Compare (==)", "Conditional/Common", "VisualShaderNodeCompare", TTR("Returns the boolean result of the comparison between two parameters."), {}, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("Is", "Conditional/Common", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between INF (or NaN) and a scalar parameter."), {}, VisualShaderNode::PORT_TYPE_BOOLEAN)); + + add_options.push_back(AddOption("BooleanConstant", "Conditional/Variables", "VisualShaderNodeBooleanConstant", TTR("Boolean constant."), {}, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("BooleanParameter", "Conditional/Variables", "VisualShaderNodeBooleanParameter", TTR("Boolean parameter."), {}, VisualShaderNode::PORT_TYPE_BOOLEAN)); // INPUT - const String input_param_shader_modes = TTR("'%s' input parameter for all shader modes."); + const String translation_gdsl = "\n\n" + TTR("Translated to '%s' in Godot Shading Language."); + const String input_param_shader_modes = TTR("'%s' input parameter for all shader modes.") + translation_gdsl; - // SPATIAL-FOR-ALL + // NODE3D-FOR-ALL - add_options.push_back(AddOption("Camera", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "camera"), "camera", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("InvCamera", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_camera"), "inv_camera", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("InvProjection", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_projection"), "inv_projection", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Normal", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "normal"), "normal", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("OutputIsSRGB", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "output_is_srgb"), "output_is_srgb", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Projection", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "projection"), "projection", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("UV", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv"), "uv", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("UV2", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv2"), "uv2", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("ViewportSize", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "viewport_size"), "viewport_size", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("World", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "world"), "world", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("InvProjectionMatrix", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_projection_matrix", "INV_PROJECTION_MATRIX"), { "inv_projection_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("InvViewMatrix", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_view_matrix", "INV_VIEW_MATRIX"), { "inv_view_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ModelMatrix", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "model_matrix", "MODEL_MATRIX"), { "model_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Normal", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "normal", "NORMAL"), { "normal" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("OutputIsSRGB", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "output_is_srgb", "OUTPUT_IS_SRGB"), { "output_is_srgb" }, VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ProjectionMatrix", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "projection_matrix", "PROJECTION_MATRIX"), { "projection_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Time", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time", "TIME"), { "time" }, VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("UV", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv", "UV"), { "uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("UV2", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv2", "UV2"), { "uv2" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewMatrix", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "view_matrix", "VIEW_MATRIX"), { "view_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewportSize", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "viewport_size", "VIEWPORT_SIZE"), { "viewport_size" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, -1, Shader::MODE_SPATIAL)); // CANVASITEM-FOR-ALL - add_options.push_back(AddOption("Alpha", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Color", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("TexturePixelSize", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "texture_pixel_size"), "texture_pixel_size", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("UV", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv"), "uv", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Color", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color", "COLOR"), { "color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, -1, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("TexturePixelSize", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "texture_pixel_size", "TEXTURE_PIXEL_SIZE"), { "texture_pixel_size" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, -1, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Time", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time", "TIME"), { "time" }, VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("UV", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv", "UV"), { "uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, -1, Shader::MODE_CANVAS_ITEM)); // PARTICLES-FOR-ALL - add_options.push_back(AddOption("Active", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Alpha", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("AttractorForce", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "attractor_force"), "attractor_force", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Color", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Custom", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("CustomAlpha", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Delta", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("EmissionTransform", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Index", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("LifeTime", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Restart", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Transform", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Velocity", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Active", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active", "ACTIVE"), { "active" }, VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("AttractorForce", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "attractor_force", "ATTRACTOR_FORCE"), { "attractor_force" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Color", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color", "COLOR"), { "color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Custom", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom", "CUSTOM"), { "custom" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Delta", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta", "DELTA"), { "delta" }, VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("EmissionTransform", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform", "EMISSION_TRANSFORM"), { "emission_transform" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Index", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index", "INDEX"), { "index" }, VisualShaderNode::PORT_TYPE_SCALAR_UINT, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("LifeTime", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime", "LIFETIME"), { "lifetime" }, VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Number", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "number", "NUMBER"), { "number" }, VisualShaderNode::PORT_TYPE_SCALAR_UINT, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("RandomSeed", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "random_seed", "RANDOM_SEED"), { "random_seed" }, VisualShaderNode::PORT_TYPE_SCALAR_UINT, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Restart", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart", "RESTART"), { "restart" }, VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Time", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time", "TIME"), { "time" }, VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Transform", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform", "TRANSFORM"), { "transform" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Velocity", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity", "VELOCITY"), { "velocity" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, -1, Shader::MODE_PARTICLES)); ///////////////// - add_options.push_back(AddOption("Input", "Input", "Common", "VisualShaderNodeInput", TTR("Input parameter."))); - - const String input_param_for_vertex_and_fragment_shader_modes = TTR("'%s' input parameter for vertex and fragment shader modes."); - const String input_param_for_fragment_and_light_shader_modes = TTR("'%s' input parameter for fragment and light shader modes."); - const String input_param_for_fragment_shader_mode = TTR("'%s' input parameter for fragment shader mode."); - const String input_param_for_sky_shader_mode = TTR("'%s' input parameter for sky shader mode."); - const String input_param_for_fog_shader_mode = TTR("'%s' input parameter for fog shader mode."); - const String input_param_for_light_shader_mode = TTR("'%s' input parameter for light shader mode."); - const String input_param_for_vertex_shader_mode = TTR("'%s' input parameter for vertex shader mode."); - const String input_param_for_start_shader_mode = TTR("'%s' input parameter for start shader mode."); - const String input_param_for_process_shader_mode = TTR("'%s' input parameter for process shader mode."); - const String input_param_for_collide_shader_mode = TTR("'%s' input parameter for collide shader mode."); - const String input_param_for_start_and_process_shader_mode = TTR("'%s' input parameter for start and process shader modes."); - const String input_param_for_process_and_collide_shader_mode = TTR("'%s' input parameter for process and collide shader modes."); - const String input_param_for_vertex_and_fragment_shader_mode = TTR("'%s' input parameter for vertex and fragment shader modes."); + add_options.push_back(AddOption("Input", "Input/Common", "VisualShaderNodeInput", TTR("Input parameter."))); + + const String input_param_for_vertex_and_fragment_shader_modes = TTR("'%s' input parameter for vertex and fragment shader modes.") + translation_gdsl; + const String input_param_for_fragment_and_light_shader_modes = TTR("'%s' input parameter for fragment and light shader modes.") + translation_gdsl; + const String input_param_for_fragment_shader_mode = TTR("'%s' input parameter for fragment shader mode.") + translation_gdsl; + const String input_param_for_sky_shader_mode = TTR("'%s' input parameter for sky shader mode.") + translation_gdsl; + const String input_param_for_fog_shader_mode = TTR("'%s' input parameter for fog shader mode.") + translation_gdsl; + const String input_param_for_light_shader_mode = TTR("'%s' input parameter for light shader mode.") + translation_gdsl; + const String input_param_for_vertex_shader_mode = TTR("'%s' input parameter for vertex shader mode.") + translation_gdsl; + const String input_param_for_start_shader_mode = TTR("'%s' input parameter for start shader mode.") + translation_gdsl; + const String input_param_for_process_shader_mode = TTR("'%s' input parameter for process shader mode.") + translation_gdsl; + const String input_param_for_collide_shader_mode = TTR("'%s' input parameter for collide shader mode." + translation_gdsl); + const String input_param_for_start_and_process_shader_mode = TTR("'%s' input parameter for start and process shader modes.") + translation_gdsl; + const String input_param_for_process_and_collide_shader_mode = TTR("'%s' input parameter for process and collide shader modes.") + translation_gdsl; + const String input_param_for_vertex_and_fragment_shader_mode = TTR("'%s' input parameter for vertex and fragment shader modes.") + translation_gdsl; // NODE3D INPUTS - add_options.push_back(AddOption("Alpha", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Binormal", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "binormal"), "binormal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Color", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("InstanceId", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_id"), "instance_id", VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("InstanceCustom", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_custom"), "instance_custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("InstanceCustomAlpha", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_custom_alpha"), "instance_custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("ModelView", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "modelview"), "modelview", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("PointSize", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "point_size"), "point_size", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Tangent", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_mode, "tangent"), "tangent", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Vertex", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "vertex"), "vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - - add_options.push_back(AddOption("Alpha", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Binormal", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "binormal"), "binormal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Color", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("DepthTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "depth_texture"), "depth_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("FragCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord"), "fragcoord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("FrontFacing", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "front_facing"), "front_facing", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("PointCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "point_coord"), "point_coord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("ScreenTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_texture"), "screen_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("ScreenUV", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Tangent", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "tangent"), "tangent", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Vertex", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "vertex"), "vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("View", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "view"), "view", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - - add_options.push_back(AddOption("Albedo", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "albedo"), "albedo", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Attenuation", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "attenuation"), "attenuation", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Backlight", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "backlight"), "backlight", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Diffuse", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "diffuse"), "diffuse", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("FragCoord", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord"), "fragcoord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Light", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light"), "light", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("LightColor", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_color"), "light_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Metallic", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "metallic"), "metallic", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Roughness", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "roughness"), "roughness", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Specular", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "specular"), "specular", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("View", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "view"), "view", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Binormal", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "binormal", "BINORMAL"), { "binormal" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Color", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "color", "COLOR"), { "color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Custom0", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "custom0", "CUSTOM0"), { "custom0" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Custom1", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "custom1", "CUSTOM1"), { "custom1" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Custom2", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "custom2", "CUSTOM2"), { "custom2" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Custom3", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "custom3", "CUSTOM3"), { "custom3" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("InstanceId", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_id", "INSTANCE_ID"), { "instance_id" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("InstanceCustom", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_custom", "INSTANCE_CUSTOM"), { "instance_custom" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ModelViewMatrix", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "modelview_matrix", "MODELVIEW_MATRIX"), { "modelview_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("PointSize", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "point_size", "POINT_SIZE"), { "point_size" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Tangent", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_mode, "tangent", "TANGENT"), { "tangent" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Vertex", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "vertex", "VERTEX"), { "vertex" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("VertexId", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "vertex_id", "VERTEX_ID"), { "vertex_id" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewIndex", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "view_index", "VIEW_INDEX"), { "view_index" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewMonoLeft", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "view_mono_left", "VIEW_MONO_LEFT"), { "view_mono_left" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewRight", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "view_right", "VIEW_RIGHT"), { "view_right" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("EyeOffset", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "eye_offset", "EYE_OFFSET"), { "eye_offset" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("NodePositionWorld", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "node_position_world", "NODE_POSITION_WORLD"), { "node_position_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("CameraPositionWorld", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "camera_position_world", "CAMERA_POSITION_WORLD"), { "camera_position_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("CameraDirectionWorld", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "camera_direction_world", "CAMERA_DIRECTION_WORLD"), { "camera_direction_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("CameraVisibleLayers", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS"), { "camera_visible_layers" }, VisualShaderNode::PORT_TYPE_SCALAR_UINT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("NodePositionView", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "node_position_view", "NODE_POSITION_VIEW"), { "node_position_view" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + + add_options.push_back(AddOption("Binormal", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "binormal", "BINORMAL"), { "binormal" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Color", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "color", "COLOR"), { "color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("FragCoord", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord", "FRAGCOORD"), { "fragcoord" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("FrontFacing", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "front_facing", "FRONT_FACING"), { "front_facing" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("PointCoord", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "point_coord", "POINT_COORD"), { "point_coord" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ScreenUV", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_uv", "SCREEN_UV"), { "screen_uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Tangent", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "tangent", "TANGENT"), { "tangent" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Vertex", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "vertex", "VERTEX"), { "vertex" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("View", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "view", "VIEW"), { "view" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewIndex", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "view_index", "VIEW_INDEX"), { "view_index" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewMonoLeft", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "view_mono_left", "VIEW_MONO_LEFT"), { "view_mono_left" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewRight", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "view_right", "VIEW_RIGHT"), { "view_right" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("EyeOffset", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "eye_offset", "EYE_OFFSET"), { "eye_offset" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("NodePositionWorld", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "node_position_world", "NODE_POSITION_WORLD"), { "node_position_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("CameraPositionWorld", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "camera_position_world", "CAMERA_POSITION_WORLD"), { "camera_position_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("CameraDirectionWorld", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "camera_direction_world", "CAMERA_DIRECTION_WORLD"), { "camera_direction_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("CameraVisibleLayers", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS"), { "camera_visible_layers" }, VisualShaderNode::PORT_TYPE_SCALAR_UINT, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("NodePositionView", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "node_position_view", "NODE_POSITION_VIEW"), { "node_position_view" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + + add_options.push_back(AddOption("Albedo", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "albedo", "ALBEDO"), { "albedo" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Attenuation", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "attenuation", "ATTENUATION"), { "attenuation" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Backlight", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "backlight", "BACKLIGHT"), { "backlight" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Diffuse", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "diffuse", "DIFFUSE_LIGHT"), { "diffuse" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("FragCoord", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord", "FRAGCOORD"), { "fragcoord" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Light", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light", "LIGHT"), { "light" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("LightColor", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_color", "LIGHT_COLOR"), { "light_color" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Metallic", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "metallic", "METALLIC"), { "metallic" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Roughness", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "roughness", "ROUGHNESS"), { "roughness" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Specular", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "specular", "SPECULAR_LIGHT"), { "specular" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("View", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "view", "VIEW"), { "view" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); // CANVASITEM INPUTS - add_options.push_back(AddOption("AtLightPass", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "at_light_pass"), "at_light_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Canvas", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "canvas"), "canvas", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("InstanceCustom", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_custom"), "instance_custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("InstanceCustomAlpha", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_custom_alpha"), "instance_custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("PointSize", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "point_size"), "point_size", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Screen", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "screen"), "screen", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Vertex", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_mode, "vertex"), "vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("World", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "world"), "world", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - - add_options.push_back(AddOption("AtLightPass", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "at_light_pass"), "at_light_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("FragCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord"), "fragcoord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("NormalTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "normal_texture"), "normal_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("PointCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "point_coord"), "point_coord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("ScreenPixelSize", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_pixel_size"), "screen_pixel_size", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("ScreenTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_texture"), "screen_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("ScreenUV", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SpecularShininess", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess"), "specular_shininess", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SpecularShininessAlpha", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess_alpha"), "specular_shininess_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SpecularShininessTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "specular_shininess_texture"), "specular_shininess_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Texture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "texture"), "texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Vertex", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_mode, "vertex"), "vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - - add_options.push_back(AddOption("FragCoord", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord"), "fragcoord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Light", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light"), "light", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightAlpha", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_alpha"), "light_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightColor", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_color"), "light_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightColorAlpha", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_color_alpha"), "light_color_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightPosition", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_position"), "light_position", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightVertex", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "light_vertex"), "light_vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Normal", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "normal"), "normal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("PointCoord", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "point_coord"), "point_coord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("ScreenUV", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Shadow", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "shadow"), "shadow", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("ShadowAlpha", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "shadow_alpha"), "shadow_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SpecularShininess", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess"), "specular_shininess", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SpecularShininessAlpha", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess_alpha"), "specular_shininess_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Texture", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "texture"), "texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("AtLightPass", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "at_light_pass", "AT_LIGHT_PASS"), { "at_light_pass" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("CanvasMatrix", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "canvas_matrix", "CANVAS_MATRIX"), { "canvas_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("InstanceCustom", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_custom", "INSTANCE_CUSTOM"), { "instance_custom" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("InstanceId", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_id", "INSTANCE_ID"), { "instance_id" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ModelMatrix", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "model_matrix", "MODEL_MATRIX"), { "model_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("PointSize", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "point_size", "POINT_SIZE"), { "point_size" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ScreenMatrix", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "screen_matrix", "SCREEN_MATRIX"), { "screen_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Vertex", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_mode, "vertex", "VERTEX"), { "vertex" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("VertexId", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "vertex_id", "VERTEX_ID"), { "vertex_id" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + + add_options.push_back(AddOption("AtLightPass", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "at_light_pass", "AT_LIGHT_PASS"), { "at_light_pass" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("FragCoord", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord", "FRAGCOORD"), { "fragcoord" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("NormalTexture", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "normal_texture", "NORMAL_TEXTURE"), { "normal_texture" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("PointCoord", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "point_coord", "POINT_COORD"), { "point_coord" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ScreenPixelSize", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_pixel_size", "SCREEN_PIXEL_SIZE"), { "screen_pixel_size" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ScreenUV", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "screen_uv", "SCREEN_UV"), { "screen_uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SpecularShininess", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess", "SPECULAR_SHININESS"), { "specular_shininess" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SpecularShininessTexture", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "specular_shininess_texture", "SPECULAR_SHININESS_TEXTURE"), { "specular_shininess_texture" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Texture", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "texture", "TEXTURE"), { "texture" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Vertex", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_mode, "vertex", "VERTEX"), { "vertex" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + + add_options.push_back(AddOption("FragCoord", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord", "FRAGCOORD"), { "fragcoord" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Light", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light", "LIGHT"), { "light" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("LightColor", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_color", "LIGHT_COLOR"), { "light_color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("LightPosition", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_position", "LIGHT_POSITION"), { "light_position" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("LightDirection", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_direction", "LIGHT_DIRECTION"), { "light_direction" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("LightIsDirectional", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_is_directional", "LIGHT_IS_DIRECTIONAL"), { "light_is_directional" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("LightEnergy", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_energy", "LIGHT_ENERGY"), { "light_energy" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("LightVertex", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "light_vertex", "LIGHT_VERTEX"), { "light_vertex" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Normal", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "normal", "NORMAL"), { "normal" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("PointCoord", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "point_coord", "POINT_COORD"), { "point_coord" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ScreenUV", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "screen_uv", "SCREEN_UV"), { "screen_uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Shadow", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "shadow", "SHADOW_MODULATE"), { "shadow" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SpecularShininess", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess", "SPECULAR_SHININESS"), { "specular_shininess" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Texture", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "texture", "TEXTURE"), { "texture" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); // SKY INPUTS - add_options.push_back(AddOption("AtCubeMapPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_cubemap_pass"), "at_cubemap_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("AtHalfResPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_half_res_pass"), "at_half_res_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("AtQuarterResPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_quarter_res_pass"), "at_quarter_res_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("EyeDir", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "eyedir"), "eyedir", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("HalfResColor", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "half_res_color"), "half_res_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("HalfResAlpha", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "half_res_alpha"), "half_res_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light0Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_color"), "light0_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light0Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_direction"), "light0_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light0Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_enabled"), "light0_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light0Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_energy"), "light0_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light1Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_color"), "light1_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light1Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_direction"), "light1_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light1Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_enabled"), "light1_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light1Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_energy"), "light1_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light2Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_color"), "light2_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light2Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_direction"), "light2_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light2Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_enabled"), "light2_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light2Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_energy"), "light2_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light3Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_color"), "light3_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light3Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_direction"), "light3_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light3Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_enabled"), "light3_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light3Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_energy"), "light3_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Position", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "position"), "position", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("QuarterResColor", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "quarter_res_color"), "quarter_res_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("QuarterResAlpha", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "quarter_res_alpha"), "quarter_res_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Radiance", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "radiance"), "radiance", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("ScreenUV", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("SkyCoords", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "sky_coords"), "sky_coords", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Time", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("AtCubeMapPass", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_cubemap_pass", "AT_CUBEMAP_PASS"), { "at_cubemap_pass" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("AtHalfResPass", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_half_res_pass", "AT_HALF_RES_PASS"), { "at_half_res_pass" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("AtQuarterResPass", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_quarter_res_pass", "AT_QUARTER_RES_PASS"), { "at_quarter_res_pass" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("EyeDir", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "eyedir", "EYEDIR"), { "eyedir" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("HalfResColor", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "half_res_color", "HALF_RES_COLOR"), { "half_res_color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light0Color", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_color", "LIGHT0_COLOR"), { "light0_color" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light0Direction", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_direction", "LIGHT0_DIRECTION"), { "light0_direction" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light0Enabled", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_enabled", "LIGHT0_ENABLED"), { "light0_enabled" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light0Energy", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_energy", "LIGHT0_ENERGY"), { "light0_energy" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light1Color", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_color", "LIGHT1_COLOR"), { "light1_color" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light1Direction", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_direction", "LIGHT1_DIRECTION"), { "light1_direction" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light1Enabled", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_enabled", "LIGHT1_ENABLED"), { "light1_enabled" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light1Energy", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_energy", "LIGHT1_ENERGY"), { "light1_energy" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light2Color", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_color", "LIGHT2_COLOR"), { "light2_color" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light2Direction", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_direction", "LIGHT2_DIRECTION"), { "light2_direction" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light2Enabled", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_enabled", "LIGHT2_ENABLED"), { "light2_enabled" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light2Energy", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_energy", "LIGHT2_ENERGY"), { "light2_energy" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light3Color", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_color", "LIGHT3_COLOR"), { "light3_color" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light3Direction", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_direction", "LIGHT3_DIRECTION"), { "light3_direction" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light3Enabled", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_enabled", "LIGHT3_ENABLED"), { "light3_enabled" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light3Energy", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_energy", "LIGHT3_ENERGY"), { "light3_energy" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Position", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "position", "POSITION"), { "position" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("QuarterResColor", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "quarter_res_color", "QUARTER_RES_COLOR"), { "quarter_res_color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Radiance", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "radiance", "RADIANCE"), { "radiance" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("ScreenUV", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "screen_uv", "SCREEN_UV"), { "screen_uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("FragCoord", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "fragcoord", "FRAGCOORD"), { "fragcoord" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + + add_options.push_back(AddOption("SkyCoords", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "sky_coords", "SKY_COORDS"), { "sky_coords" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Time", "Input/Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "time", "TIME"), { "time" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); // FOG INPUTS - add_options.push_back(AddOption("WorldPosition", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "world_position"), "world_position", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); - add_options.push_back(AddOption("ObjectPosition", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "object_position"), "object_position", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); - add_options.push_back(AddOption("UVW", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "uvw"), "uvw", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); - add_options.push_back(AddOption("Extents", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "extents"), "extents", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); - add_options.push_back(AddOption("Transform", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_FOG, Shader::MODE_FOG)); - add_options.push_back(AddOption("SDF", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "sdf"), "sdf", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); - add_options.push_back(AddOption("Time", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("WorldPosition", "Input/Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "world_position", "WORLD_POSITION"), { "world_position" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("ObjectPosition", "Input/Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "object_position", "OBJECT_POSITION"), { "object_position" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("UVW", "Input/Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "uvw", "UVW"), { "uvw" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("Size", "Input/Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "size", "SIZE"), { "size" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("SDF", "Input/Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "sdf", "SDF"), { "sdf" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("Time", "Input/Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "time", "TIME"), { "time" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); // PARTICLES INPUTS - add_options.push_back(AddOption("CollisionDepth", "Input", "Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_depth"), "collision_depth", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("CollisionNormal", "Input", "Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_normal"), "collision_normal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("CollisionDepth", "Input/Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_depth", "COLLISION_DEPTH"), { "collision_depth" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("CollisionNormal", "Input/Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_normal", "COLLISION_NORMAL"), { "collision_normal" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); // PARTICLES - add_options.push_back(AddOption("EmitParticle", "Particles", "", "VisualShaderNodeParticleEmit", "", -1, -1, TYPE_FLAGS_PROCESS | TYPE_FLAGS_PROCESS_CUSTOM | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("ParticleAccelerator", "Particles", "", "VisualShaderNodeParticleAccelerator", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("ParticleRandomness", "Particles", "", "VisualShaderNodeParticleRandomness", "", -1, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("MultiplyByAxisAngle", "Particles", "Transform", "VisualShaderNodeParticleMultiplyByAxisAngle", "A node for help to multiply a position input vector by rotation using specific axis. Intended to work with emitters.", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("EmitParticle", "Particles", "VisualShaderNodeParticleEmit", "", {}, -1, TYPE_FLAGS_PROCESS | TYPE_FLAGS_PROCESS_CUSTOM | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("ParticleAccelerator", "Particles", "VisualShaderNodeParticleAccelerator", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("ParticleRandomness", "Particles", "VisualShaderNodeParticleRandomness", "", {}, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("MultiplyByAxisAngle (*)", "Particles/Transform", "VisualShaderNodeParticleMultiplyByAxisAngle", TTR("A node for help to multiply a position input vector by rotation using specific axis. Intended to work with emitters."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("BoxEmitter", "Particles", "Emitters", "VisualShaderNodeParticleBoxEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("MeshEmitter", "Particles", "Emitters", "VisualShaderNodeParticleMeshEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("RingEmitter", "Particles", "Emitters", "VisualShaderNodeParticleRingEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("SphereEmitter", "Particles", "Emitters", "VisualShaderNodeParticleSphereEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("BoxEmitter", "Particles/Emitters", "VisualShaderNodeParticleBoxEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("MeshEmitter", "Particles/Emitters", "VisualShaderNodeParticleMeshEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("RingEmitter", "Particles/Emitters", "VisualShaderNodeParticleRingEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("SphereEmitter", "Particles/Emitters", "VisualShaderNodeParticleSphereEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("ConeVelocity", "Particles", "Velocity", "VisualShaderNodeParticleConeVelocity", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("ConeVelocity", "Particles/Velocity", "VisualShaderNodeParticleConeVelocity", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); // SCALAR - add_options.push_back(AddOption("FloatFunc", "Scalar", "Common", "VisualShaderNodeFloatFunc", TTR("Float function."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("IntFunc", "Scalar", "Common", "VisualShaderNodeIntFunc", TTR("Integer function."), -1, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("FloatOp", "Scalar", "Common", "VisualShaderNodeFloatOp", TTR("Float operator."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("IntOp", "Scalar", "Common", "VisualShaderNodeIntOp", TTR("Integer operator."), -1, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("FloatFunc", "Scalar/Common", "VisualShaderNodeFloatFunc", TTR("Float function."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("FloatOp", "Scalar/Common", "VisualShaderNodeFloatOp", TTR("Float operator."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("IntFunc", "Scalar/Common", "VisualShaderNodeIntFunc", TTR("Integer function."), {}, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("IntOp", "Scalar/Common", "VisualShaderNodeIntOp", TTR("Integer operator."), {}, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("UIntFunc", "Scalar/Common", "VisualShaderNodeUIntFunc", TTR("Unsigned integer function."), {}, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("UIntOp", "Scalar/Common", "VisualShaderNodeUIntOp", TTR("Unsigned integer operator."), {}, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); // CONSTANTS for (int i = 0; i < MAX_FLOAT_CONST_DEFS; i++) { - add_options.push_back(AddOption(float_constant_defs[i].name, "Scalar", "Constants", "VisualShaderNodeFloatConstant", float_constant_defs[i].desc, -1, VisualShaderNode::PORT_TYPE_SCALAR, -1, -1, float_constant_defs[i].value)); + add_options.push_back(AddOption(float_constant_defs[i].name, "Scalar/Constants", "VisualShaderNodeFloatConstant", float_constant_defs[i].desc, { float_constant_defs[i].value }, VisualShaderNode::PORT_TYPE_SCALAR)); } // FUNCTIONS - add_options.push_back(AddOption("Abs", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the absolute value of the parameter."), VisualShaderNodeFloatFunc::FUNC_ABS, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Abs", "Scalar", "Functions", "VisualShaderNodeIntFunc", TTR("Returns the absolute value of the parameter."), VisualShaderNodeIntFunc::FUNC_ABS, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("ACos", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the arc-cosine of the parameter."), VisualShaderNodeFloatFunc::FUNC_ACOS, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("ACosH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse hyperbolic cosine of the parameter."), VisualShaderNodeFloatFunc::FUNC_ACOSH, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("ASin", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the arc-sine of the parameter."), VisualShaderNodeFloatFunc::FUNC_ASIN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("ASinH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse hyperbolic sine of the parameter."), VisualShaderNodeFloatFunc::FUNC_ASINH, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("ATan", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the arc-tangent of the parameter."), VisualShaderNodeFloatFunc::FUNC_ATAN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("ATan2", "Scalar", "Functions", "VisualShaderNodeFloatOp", TTR("Returns the arc-tangent of the parameters."), VisualShaderNodeFloatOp::OP_ATAN2, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("ATanH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), VisualShaderNodeFloatFunc::FUNC_ATANH, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("BitwiseNOT", "Scalar", "Functions", "VisualShaderNodeIntFunc", TTR("Returns the result of bitwise NOT (~a) operation on the integer."), VisualShaderNodeIntFunc::FUNC_BITWISE_NOT, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Ceil", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), VisualShaderNodeFloatFunc::FUNC_CEIL, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Clamp", "Scalar", "Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), VisualShaderNodeClamp::OP_TYPE_FLOAT, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Clamp", "Scalar", "Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), VisualShaderNodeClamp::OP_TYPE_INT, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Cos", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the cosine of the parameter."), VisualShaderNodeFloatFunc::FUNC_COS, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("CosH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the hyperbolic cosine of the parameter."), VisualShaderNodeFloatFunc::FUNC_COSH, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Degrees", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Converts a quantity in radians to degrees."), VisualShaderNodeFloatFunc::FUNC_DEGREES, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Exp", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Base-e Exponential."), VisualShaderNodeFloatFunc::FUNC_EXP, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Exp2", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Base-2 Exponential."), VisualShaderNodeFloatFunc::FUNC_EXP2, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Floor", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer less than or equal to the parameter."), VisualShaderNodeFloatFunc::FUNC_FLOOR, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Fract", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Computes the fractional part of the argument."), VisualShaderNodeFloatFunc::FUNC_FRAC, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("InverseSqrt", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse of the square root of the parameter."), VisualShaderNodeFloatFunc::FUNC_INVERSE_SQRT, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Log", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Natural logarithm."), VisualShaderNodeFloatFunc::FUNC_LOG, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Log2", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Base-2 logarithm."), VisualShaderNodeFloatFunc::FUNC_LOG2, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Max", "Scalar", "Functions", "VisualShaderNodeFloatOp", TTR("Returns the greater of two values."), VisualShaderNodeFloatOp::OP_MAX, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Min", "Scalar", "Functions", "VisualShaderNodeFloatOp", TTR("Returns the lesser of two values."), VisualShaderNodeFloatOp::OP_MIN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Mix", "Scalar", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two scalars."), VisualShaderNodeMix::OP_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("MultiplyAdd", "Scalar", "Functions", "VisualShaderNodeMultiplyAdd", TTR("Performs a fused multiply-add operation (a * b + c) on scalars."), VisualShaderNodeMultiplyAdd::OP_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Negate", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the opposite value of the parameter."), VisualShaderNodeFloatFunc::FUNC_NEGATE, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Negate", "Scalar", "Functions", "VisualShaderNodeIntFunc", TTR("Returns the opposite value of the parameter."), VisualShaderNodeIntFunc::FUNC_NEGATE, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("OneMinus", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("1.0 - scalar"), VisualShaderNodeFloatFunc::FUNC_ONEMINUS, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Pow", "Scalar", "Functions", "VisualShaderNodeFloatOp", TTR("Returns the value of the first parameter raised to the power of the second."), VisualShaderNodeFloatOp::OP_POW, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Radians", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Converts a quantity in degrees to radians."), VisualShaderNodeFloatFunc::FUNC_RADIANS, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Reciprocal", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("1.0 / scalar"), VisualShaderNodeFloatFunc::FUNC_RECIPROCAL, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Round", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer to the parameter."), VisualShaderNodeFloatFunc::FUNC_ROUND, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("RoundEven", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest even integer to the parameter."), VisualShaderNodeFloatFunc::FUNC_ROUNDEVEN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Saturate", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Clamps the value between 0.0 and 1.0."), VisualShaderNodeFloatFunc::FUNC_SATURATE, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Sign", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Extracts the sign of the parameter."), VisualShaderNodeFloatFunc::FUNC_SIGN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Sign", "Scalar", "Functions", "VisualShaderNodeIntFunc", TTR("Extracts the sign of the parameter."), VisualShaderNodeIntFunc::FUNC_SIGN, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Sin", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the sine of the parameter."), VisualShaderNodeFloatFunc::FUNC_SIN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("SinH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the hyperbolic sine of the parameter."), VisualShaderNodeFloatFunc::FUNC_SINH, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Sqrt", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the square root of the parameter."), VisualShaderNodeFloatFunc::FUNC_SQRT, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("SmoothStep", "Scalar", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( scalar(edge0), scalar(edge1), scalar(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if x is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), VisualShaderNodeSmoothStep::OP_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Step", "Scalar", "Functions", "VisualShaderNodeStep", TTR("Step function( scalar(edge), scalar(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), VisualShaderNodeStep::OP_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Tan", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the tangent of the parameter."), VisualShaderNodeFloatFunc::FUNC_TAN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("TanH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the hyperbolic tangent of the parameter."), VisualShaderNodeFloatFunc::FUNC_TANH, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Trunc", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the truncated value of the parameter."), VisualShaderNodeFloatFunc::FUNC_TRUNC, VisualShaderNode::PORT_TYPE_SCALAR)); - - add_options.push_back(AddOption("Add", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Sums two floating-point scalars."), VisualShaderNodeFloatOp::OP_ADD, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Add", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Sums two integer scalars."), VisualShaderNodeIntOp::OP_ADD, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("BitwiseAND", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise AND (a & b) operation for two integers."), VisualShaderNodeIntOp::OP_BITWISE_AND, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("BitwiseLeftShift", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise left shift (a << b) operation on the integer."), VisualShaderNodeIntOp::OP_BITWISE_LEFT_SHIFT, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("BitwiseOR", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise OR (a | b) operation for two integers."), VisualShaderNodeIntOp::OP_BITWISE_OR, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("BitwiseRightShift", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise right shift (a >> b) operation on the integer."), VisualShaderNodeIntOp::OP_BITWISE_RIGHT_SHIFT, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("BitwiseXOR", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise XOR (a ^ b) operation on the integer."), VisualShaderNodeIntOp::OP_BITWISE_XOR, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Divide", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Divides two floating-point scalars."), VisualShaderNodeFloatOp::OP_DIV, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Divide", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Divides two integer scalars."), VisualShaderNodeIntOp::OP_DIV, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Multiply", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Multiplies two floating-point scalars."), VisualShaderNodeFloatOp::OP_MUL, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Multiply", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Multiplies two integer scalars."), VisualShaderNodeIntOp::OP_MUL, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Remainder", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Returns the remainder of the two floating-point scalars."), VisualShaderNodeFloatOp::OP_MOD, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Remainder", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the remainder of the two integer scalars."), VisualShaderNodeIntOp::OP_MOD, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Subtract", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Subtracts two floating-point scalars."), VisualShaderNodeFloatOp::OP_SUB, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Subtract", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Subtracts two integer scalars."), VisualShaderNodeIntOp::OP_SUB, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - - add_options.push_back(AddOption("FloatConstant", "Scalar", "Variables", "VisualShaderNodeFloatConstant", TTR("Scalar floating-point constant."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("IntConstant", "Scalar", "Variables", "VisualShaderNodeIntConstant", TTR("Scalar integer constant."), -1, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("FloatUniform", "Scalar", "Variables", "VisualShaderNodeFloatUniform", TTR("Scalar floating-point uniform."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("IntUniform", "Scalar", "Variables", "VisualShaderNodeIntUniform", TTR("Scalar integer uniform."), -1, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Abs", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the absolute value of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ABS }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Abs", "Scalar/Functions", "VisualShaderNodeIntFunc", TTR("Returns the absolute value of the parameter."), { VisualShaderNodeIntFunc::FUNC_ABS }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("ACos", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the arc-cosine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ACOS }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("ACosH", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse hyperbolic cosine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ACOSH }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("ASin", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the arc-sine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ASIN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("ASinH", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse hyperbolic sine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ASINH }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("ATan", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the arc-tangent of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ATAN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("ATan2", "Scalar/Functions", "VisualShaderNodeFloatOp", TTR("Returns the arc-tangent of the parameters."), { VisualShaderNodeFloatOp::OP_ATAN2 }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("ATanH", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ATANH }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("BitwiseNOT", "Scalar/Functions", "VisualShaderNodeIntFunc", TTR("Returns the result of bitwise NOT (~a) operation on the integer."), { VisualShaderNodeIntFunc::FUNC_BITWISE_NOT }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("BitwiseNOT", "Scalar/Functions", "VisualShaderNodeUIntFunc", TTR("Returns the result of bitwise NOT (~a) operation on the unsigned integer."), { VisualShaderNodeUIntFunc::FUNC_BITWISE_NOT }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("Ceil", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), { VisualShaderNodeFloatFunc::FUNC_CEIL }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Clamp", "Scalar/Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), { VisualShaderNodeClamp::OP_TYPE_FLOAT }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Clamp", "Scalar/Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), { VisualShaderNodeClamp::OP_TYPE_INT }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Clamp", "Scalar/Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), { VisualShaderNodeClamp::OP_TYPE_UINT }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("Cos", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the cosine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_COS }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("CosH", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the hyperbolic cosine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_COSH }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Degrees", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Converts a quantity in radians to degrees."), { VisualShaderNodeFloatFunc::FUNC_DEGREES }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("DFdX", "Scalar/Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Derivative in 'x' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_X, VisualShaderNodeDerivativeFunc::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("DFdY", "Scalar/Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Derivative in 'y' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_Y, VisualShaderNodeDerivativeFunc::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("Exp", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Base-e Exponential."), { VisualShaderNodeFloatFunc::FUNC_EXP }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Exp2", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Base-2 Exponential."), { VisualShaderNodeFloatFunc::FUNC_EXP2 }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Floor", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer less than or equal to the parameter."), { VisualShaderNodeFloatFunc::FUNC_FLOOR }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Fract", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeFloatFunc::FUNC_FRACT }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("InverseSqrt", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse of the square root of the parameter."), { VisualShaderNodeFloatFunc::FUNC_INVERSE_SQRT }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Log", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Natural logarithm."), { VisualShaderNodeFloatFunc::FUNC_LOG }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Log2", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Base-2 logarithm."), { VisualShaderNodeFloatFunc::FUNC_LOG2 }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Max", "Scalar/Functions", "VisualShaderNodeFloatOp", TTR("Returns the greater of two values."), { VisualShaderNodeFloatOp::OP_MAX }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Min", "Scalar/Functions", "VisualShaderNodeFloatOp", TTR("Returns the lesser of two values."), { VisualShaderNodeFloatOp::OP_MIN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Mix", "Scalar/Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two scalars."), { VisualShaderNodeMix::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("MultiplyAdd (a * b + c)", "Scalar/Functions", "VisualShaderNodeMultiplyAdd", TTR("Performs a fused multiply-add operation (a * b + c) on scalars."), { VisualShaderNodeMultiplyAdd::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Negate (*-1)", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the opposite value of the parameter."), { VisualShaderNodeFloatFunc::FUNC_NEGATE }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Negate (*-1)", "Scalar/Functions", "VisualShaderNodeIntFunc", TTR("Returns the opposite value of the parameter."), { VisualShaderNodeIntFunc::FUNC_NEGATE }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Negate (*-1)", "Scalar/Functions", "VisualShaderNodeUIntFunc", TTR("Returns the opposite value of the parameter."), { VisualShaderNodeUIntFunc::FUNC_NEGATE }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("OneMinus (1-)", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("1.0 - scalar"), { VisualShaderNodeFloatFunc::FUNC_ONEMINUS }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Pow", "Scalar/Functions", "VisualShaderNodeFloatOp", TTR("Returns the value of the first parameter raised to the power of the second."), { VisualShaderNodeFloatOp::OP_POW }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Radians", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Converts a quantity in degrees to radians."), { VisualShaderNodeFloatFunc::FUNC_RADIANS }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Reciprocal", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("1.0 / scalar"), { VisualShaderNodeFloatFunc::FUNC_RECIPROCAL }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Round", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer to the parameter."), { VisualShaderNodeFloatFunc::FUNC_ROUND }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("RoundEven", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest even integer to the parameter."), { VisualShaderNodeFloatFunc::FUNC_ROUNDEVEN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Saturate", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Clamps the value between 0.0 and 1.0."), { VisualShaderNodeFloatFunc::FUNC_SATURATE }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Sign", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Extracts the sign of the parameter."), { VisualShaderNodeFloatFunc::FUNC_SIGN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Sign", "Scalar/Functions", "VisualShaderNodeIntFunc", TTR("Extracts the sign of the parameter."), { VisualShaderNodeIntFunc::FUNC_SIGN }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Sin", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the sine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_SIN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("SinH", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the hyperbolic sine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_SINH }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Sqrt", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the square root of the parameter."), { VisualShaderNodeFloatFunc::FUNC_SQRT }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("SmoothStep", "Scalar/Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( scalar(edge0), scalar(edge1), scalar(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if x is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Step", "Scalar/Functions", "VisualShaderNodeStep", TTR("Step function( scalar(edge), scalar(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), { VisualShaderNodeStep::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Sum", "Scalar/Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Sum of absolute derivative in 'x' and 'y'."), { VisualShaderNodeDerivativeFunc::FUNC_SUM, VisualShaderNodeDerivativeFunc::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("Tan", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the tangent of the parameter."), { VisualShaderNodeFloatFunc::FUNC_TAN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("TanH", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Returns the hyperbolic tangent of the parameter."), { VisualShaderNodeFloatFunc::FUNC_TANH }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Trunc", "Scalar/Functions", "VisualShaderNodeFloatFunc", TTR("Finds the truncated value of the parameter."), { VisualShaderNodeFloatFunc::FUNC_TRUNC }, VisualShaderNode::PORT_TYPE_SCALAR)); + + add_options.push_back(AddOption("Add (+)", "Scalar/Operators", "VisualShaderNodeFloatOp", TTR("Sums two floating-point scalars."), { VisualShaderNodeFloatOp::OP_ADD }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Add (+)", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Sums two integer scalars."), { VisualShaderNodeIntOp::OP_ADD }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Add (+)", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Sums two unsigned integer scalars."), { VisualShaderNodeUIntOp::OP_ADD }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("BitwiseAND (&)", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise AND (a & b) operation for two integers."), { VisualShaderNodeIntOp::OP_BITWISE_AND }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("BitwiseAND (&)", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Returns the result of bitwise AND (a & b) operation for two unsigned integers."), { VisualShaderNodeUIntOp::OP_BITWISE_AND }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("BitwiseLeftShift (<<)", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise left shift (a << b) operation on the integer."), { VisualShaderNodeIntOp::OP_BITWISE_LEFT_SHIFT }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("BitwiseLeftShift (<<)", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Returns the result of bitwise left shift (a << b) operation on the unsigned integer."), { VisualShaderNodeUIntOp::OP_BITWISE_LEFT_SHIFT }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("BitwiseOR (|)", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise OR (a | b) operation for two integers."), { VisualShaderNodeIntOp::OP_BITWISE_OR }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("BitwiseOR (|)", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Returns the result of bitwise OR (a | b) operation for two unsigned integers."), { VisualShaderNodeUIntOp::OP_BITWISE_OR }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("BitwiseRightShift (>>)", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise right shift (a >> b) operation on the integer."), { VisualShaderNodeIntOp::OP_BITWISE_RIGHT_SHIFT }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("BitwiseRightShift (>>)", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Returns the result of bitwise right shift (a >> b) operation on the unsigned integer."), { VisualShaderNodeIntOp::OP_BITWISE_RIGHT_SHIFT }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("BitwiseXOR (^)", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise XOR (a ^ b) operation on the integer."), { VisualShaderNodeIntOp::OP_BITWISE_XOR }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("BitwiseXOR (^)", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Returns the result of bitwise XOR (a ^ b) operation on the unsigned integer."), { VisualShaderNodeUIntOp::OP_BITWISE_XOR }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("Divide (/)", "Scalar/Operators", "VisualShaderNodeFloatOp", TTR("Divides two floating-point scalars."), { VisualShaderNodeFloatOp::OP_DIV }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Divide (/)", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Divides two integer scalars."), { VisualShaderNodeIntOp::OP_DIV }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Divide (/)", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Divides two unsigned integer scalars."), { VisualShaderNodeUIntOp::OP_DIV }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("Multiply (*)", "Scalar/Operators", "VisualShaderNodeFloatOp", TTR("Multiplies two floating-point scalars."), { VisualShaderNodeFloatOp::OP_MUL }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Multiply (*)", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Multiplies two integer scalars."), { VisualShaderNodeIntOp::OP_MUL }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Multiply (*)", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Multiplies two unsigned integer scalars."), { VisualShaderNodeUIntOp::OP_MUL }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("Remainder", "Scalar/Operators", "VisualShaderNodeFloatOp", TTR("Returns the remainder of the two floating-point scalars."), { VisualShaderNodeFloatOp::OP_MOD }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Remainder", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Returns the remainder of the two integer scalars."), { VisualShaderNodeIntOp::OP_MOD }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Remainder", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Returns the remainder of the two unsigned integer scalars."), { VisualShaderNodeUIntOp::OP_MOD }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("Subtract (-)", "Scalar/Operators", "VisualShaderNodeFloatOp", TTR("Subtracts two floating-point scalars."), { VisualShaderNodeFloatOp::OP_SUB }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Subtract (-)", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Subtracts two integer scalars."), { VisualShaderNodeIntOp::OP_SUB }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Subtract (-)", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Subtracts two unsigned integer scalars."), { VisualShaderNodeUIntOp::OP_SUB }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + + add_options.push_back(AddOption("FloatConstant", "Scalar/Variables", "VisualShaderNodeFloatConstant", TTR("Scalar floating-point constant."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("IntConstant", "Scalar/Variables", "VisualShaderNodeIntConstant", TTR("Scalar integer constant."), {}, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("UIntConstant", "Scalar/Variables", "VisualShaderNodeUIntConstant", TTR("Scalar unsigned integer constant."), {}, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("FloatParameter", "Scalar/Variables", "VisualShaderNodeFloatParameter", TTR("Scalar floating-point parameter."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("IntParameter", "Scalar/Variables", "VisualShaderNodeIntParameter", TTR("Scalar integer parameter."), {}, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("UIntParameter", "Scalar/Variables", "VisualShaderNodeUIntParameter", TTR("Scalar unsigned integer parameter."), {}, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); // SDF { - add_options.push_back(AddOption("ScreenUVToSDF", "SDF", "", "VisualShaderNodeScreenUVToSDF", TTR("Converts screen UV to a SDF."), -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SDFRaymarch", "SDF", "", "VisualShaderNodeSDFRaymarch", TTR("Casts a ray against the screen SDF and returns the distance travelled."), -1, -1, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SDFToScreenUV", "SDF", "", "VisualShaderNodeSDFToScreenUV", TTR("Converts a SDF to screen UV."), -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("TextureSDF", "SDF", "", "VisualShaderNodeTextureSDF", TTR("Performs a SDF texture lookup."), -1, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("TextureSDFNormal", "SDF", "", "VisualShaderNodeTextureSDFNormal", TTR("Performs a SDF normal texture lookup."), -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ScreenUVToSDF", "SDF", "VisualShaderNodeScreenUVToSDF", TTR("Converts screen UV to a SDF."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SDFRaymarch", "SDF", "VisualShaderNodeSDFRaymarch", TTR("Casts a ray against the screen SDF and returns the distance travelled."), {}, -1, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SDFToScreenUV", "SDF", "VisualShaderNodeSDFToScreenUV", TTR("Converts a SDF to screen UV."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("TextureSDF", "SDF", "VisualShaderNodeTextureSDF", TTR("Performs a SDF texture lookup."), {}, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("TextureSDFNormal", "SDF", "VisualShaderNodeTextureSDFNormal", TTR("Performs a SDF normal texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); } // TEXTURES - add_options.push_back(AddOption("UVFunc", "Textures", "Common", "VisualShaderNodeUVFunc", TTR("Function to be applied on texture coordinates."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("UVFunc", "Textures/Common", "VisualShaderNodeUVFunc", TTR("Function to be applied on texture coordinates."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("UVPolarCoord", "Textures/Common", "VisualShaderNodeUVPolarCoord", TTR("Polar coordinates conversion applied on texture coordinates."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D)); cubemap_node_option_idx = add_options.size(); - add_options.push_back(AddOption("CubeMap", "Textures", "Functions", "VisualShaderNodeCubemap", TTR("Perform the cubic texture lookup."), -1, -1)); + add_options.push_back(AddOption("CubeMap", "Textures/Functions", "VisualShaderNodeCubemap", TTR("Perform the cubic texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); curve_node_option_idx = add_options.size(); - add_options.push_back(AddOption("CurveTexture", "Textures", "Functions", "VisualShaderNodeCurveTexture", TTR("Perform the curve texture lookup."), -1, -1)); + add_options.push_back(AddOption("CurveTexture", "Textures/Functions", "VisualShaderNodeCurveTexture", TTR("Perform the curve texture lookup."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); curve_xyz_node_option_idx = add_options.size(); - add_options.push_back(AddOption("CurveXYZTexture", "Textures", "Functions", "VisualShaderNodeCurveXYZTexture", TTR("Perform the three components curve texture lookup."), -1, -1)); + add_options.push_back(AddOption("CurveXYZTexture", "Textures/Functions", "VisualShaderNodeCurveXYZTexture", TTR("Perform the three components curve texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("LinearSceneDepth", "Textures/Functions", "VisualShaderNodeLinearSceneDepth", TTR("Returns the depth value obtained from the depth prepass in a linear space."), {}, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); texture2d_node_option_idx = add_options.size(); - add_options.push_back(AddOption("Texture2D", "Textures", "Functions", "VisualShaderNodeTexture", TTR("Perform the 2D texture lookup."), -1, -1)); + add_options.push_back(AddOption("Texture2D", "Textures/Functions", "VisualShaderNodeTexture", TTR("Perform the 2D texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); texture2d_array_node_option_idx = add_options.size(); - add_options.push_back(AddOption("Texture2DArray", "Textures", "Functions", "VisualShaderNodeTexture2DArray", TTR("Perform the 2D-array texture lookup."), -1, -1, -1, -1, -1)); + add_options.push_back(AddOption("Texture2DArray", "Textures/Functions", "VisualShaderNodeTexture2DArray", TTR("Perform the 2D-array texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); texture3d_node_option_idx = add_options.size(); - add_options.push_back(AddOption("Texture3D", "Textures", "Functions", "VisualShaderNodeTexture3D", TTR("Perform the 3D texture lookup."), -1, -1)); - add_options.push_back(AddOption("UVPanning", "Textures", "Functions", "VisualShaderNodeUVFunc", TTR("Apply panning function on texture coordinates."), VisualShaderNodeUVFunc::FUNC_PANNING, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("UVScaling", "Textures", "Functions", "VisualShaderNodeUVFunc", TTR("Apply scaling function on texture coordinates."), VisualShaderNodeUVFunc::FUNC_SCALING, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("Texture3D", "Textures/Functions", "VisualShaderNodeTexture3D", TTR("Perform the 3D texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("UVPanning", "Textures/Functions", "VisualShaderNodeUVFunc", TTR("Apply panning function on texture coordinates."), { VisualShaderNodeUVFunc::FUNC_PANNING }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("UVScaling", "Textures/Functions", "VisualShaderNodeUVFunc", TTR("Apply scaling function on texture coordinates."), { VisualShaderNodeUVFunc::FUNC_SCALING }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); - add_options.push_back(AddOption("CubeMapUniform", "Textures", "Variables", "VisualShaderNodeCubemapUniform", TTR("Cubic texture uniform lookup."), -1, -1)); - add_options.push_back(AddOption("TextureUniform", "Textures", "Variables", "VisualShaderNodeTextureUniform", TTR("2D texture uniform lookup."), -1, -1)); - add_options.push_back(AddOption("TextureUniformTriplanar", "Textures", "Variables", "VisualShaderNodeTextureUniformTriplanar", TTR("2D texture uniform lookup with triplanar."), -1, -1, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Texture2DArrayUniform", "Textures", "Variables", "VisualShaderNodeTexture2DArrayUniform", TTR("2D array of textures uniform lookup."), -1, -1, -1, -1, -1)); - add_options.push_back(AddOption("Texture3DUniform", "Textures", "Variables", "VisualShaderNodeTexture3DUniform", TTR("3D texture uniform lookup."), -1, -1, -1, -1, -1)); + add_options.push_back(AddOption("CubeMapParameter", "Textures/Variables", "VisualShaderNodeCubemapParameter", TTR("Cubic texture parameter lookup."), {}, VisualShaderNode::PORT_TYPE_SAMPLER)); + add_options.push_back(AddOption("Texture2DParameter", "Textures/Variables", "VisualShaderNodeTexture2DParameter", TTR("2D texture parameter lookup."), {}, VisualShaderNode::PORT_TYPE_SAMPLER)); + add_options.push_back(AddOption("TextureParameterTriplanar", "Textures/Variables", "VisualShaderNodeTextureParameterTriplanar", TTR("2D texture parameter lookup with triplanar."), {}, -1, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Texture2DArrayParameter", "Textures/Variables", "VisualShaderNodeTexture2DArrayParameter", TTR("2D array of textures parameter lookup."), {}, VisualShaderNode::PORT_TYPE_SAMPLER)); + add_options.push_back(AddOption("Texture3DParameter", "Textures/Variables", "VisualShaderNodeTexture3DParameter", TTR("3D texture parameter lookup."), {}, VisualShaderNode::PORT_TYPE_SAMPLER)); // TRANSFORM - add_options.push_back(AddOption("TransformFunc", "Transform", "Common", "VisualShaderNodeTransformFunc", TTR("Transform function."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("TransformOp", "Transform", "Common", "VisualShaderNodeTransformOp", TTR("Transform operator."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformFunc", "Transform/Common", "VisualShaderNodeTransformFunc", TTR("Transform function."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformOp", "Transform/Common", "VisualShaderNodeTransformOp", TTR("Transform operator."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM)); + + add_options.push_back(AddOption("OuterProduct", "Transform/Composition", "VisualShaderNodeOuterProduct", TTR("Calculate the outer product of a pair of vectors.\n\nOuterProduct treats the first parameter 'c' as a column vector (matrix with one column) and the second parameter 'r' as a row vector (matrix with one row) and does a linear algebraic matrix multiply 'c * r', yielding a matrix whose number of rows is the number of components in 'c' and whose number of columns is the number of components in 'r'."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformCompose", "Transform/Composition", "VisualShaderNodeTransformCompose", TTR("Composes transform from four vectors."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformDecompose", "Transform/Composition", "VisualShaderNodeTransformDecompose", TTR("Decomposes transform to four vectors."))); + + add_options.push_back(AddOption("Determinant", "Transform/Functions", "VisualShaderNodeDeterminant", TTR("Calculates the determinant of a transform."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("GetBillboardMatrix", "Transform/Functions", "VisualShaderNodeBillboard", TTR("Calculates how the object should face the camera to be applied on Model View Matrix output port for 3D objects."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Inverse", "Transform/Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the inverse of a transform."), { VisualShaderNodeTransformFunc::FUNC_INVERSE }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("Transpose", "Transform/Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the transpose of a transform."), { VisualShaderNodeTransformFunc::FUNC_TRANSPOSE }, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("OuterProduct", "Transform", "Composition", "VisualShaderNodeOuterProduct", TTR("Calculate the outer product of a pair of vectors.\n\nOuterProduct treats the first parameter 'c' as a column vector (matrix with one column) and the second parameter 'r' as a row vector (matrix with one row) and does a linear algebraic matrix multiply 'c * r', yielding a matrix whose number of rows is the number of components in 'c' and whose number of columns is the number of components in 'r'."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("TransformCompose", "Transform", "Composition", "VisualShaderNodeTransformCompose", TTR("Composes transform from four vectors."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("TransformDecompose", "Transform", "Composition", "VisualShaderNodeTransformDecompose", TTR("Decomposes transform to four vectors."))); + add_options.push_back(AddOption("Add (+)", "Transform/Operators", "VisualShaderNodeTransformOp", TTR("Sums two transforms."), { VisualShaderNodeTransformOp::OP_ADD }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("Divide (/)", "Transform/Operators", "VisualShaderNodeTransformOp", TTR("Divides two transforms."), { VisualShaderNodeTransformOp::OP_A_DIV_B }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("Multiply (*)", "Transform/Operators", "VisualShaderNodeTransformOp", TTR("Multiplies two transforms."), { VisualShaderNodeTransformOp::OP_AxB }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("MultiplyComp (*)", "Transform/Operators", "VisualShaderNodeTransformOp", TTR("Performs per-component multiplication of two transforms."), { VisualShaderNodeTransformOp::OP_AxB_COMP }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("Subtract (-)", "Transform/Operators", "VisualShaderNodeTransformOp", TTR("Subtracts two transforms."), { VisualShaderNodeTransformOp::OP_A_MINUS_B }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformVectorMult (*)", "Transform/Operators", "VisualShaderNodeTransformVecMult", TTR("Multiplies vector by transform."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); - add_options.push_back(AddOption("Determinant", "Transform", "Functions", "VisualShaderNodeDeterminant", TTR("Calculates the determinant of a transform."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("GetBillboardMatrix", "Transform", "Functions", "VisualShaderNodeBillboard", TTR("Calculates how the object should face the camera to be applied on Model View Matrix output port for 3D objects."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Inverse", "Transform", "Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the inverse of a transform."), VisualShaderNodeTransformFunc::FUNC_INVERSE, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("Transpose", "Transform", "Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the transpose of a transform."), VisualShaderNodeTransformFunc::FUNC_TRANSPOSE, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformConstant", "Transform/Variables", "VisualShaderNodeTransformConstant", TTR("Transform constant."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformParameter", "Transform/Variables", "VisualShaderNodeTransformParameter", TTR("Transform parameter."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("Add", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Sums two transforms."), VisualShaderNodeTransformOp::OP_ADD, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("Divide", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Divides two transforms."), VisualShaderNodeTransformOp::OP_A_DIV_B, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("Multiply", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Multiplies two transforms."), VisualShaderNodeTransformOp::OP_AxB, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("MultiplyComp", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Performs per-component multiplication of two transforms."), VisualShaderNodeTransformOp::OP_AxB_COMP, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("Subtract", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Subtracts two transforms."), VisualShaderNodeTransformOp::OP_A_MINUS_B, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("TransformVectorMult", "Transform", "Operators", "VisualShaderNodeTransformVecMult", TTR("Multiplies vector by transform."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); + // UTILITY - add_options.push_back(AddOption("TransformConstant", "Transform", "Variables", "VisualShaderNodeTransformConstant", TTR("Transform constant."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("TransformUniform", "Transform", "Variables", "VisualShaderNodeTransformUniform", TTR("Transform uniform."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("DistanceFade", "Utility", "VisualShaderNodeDistanceFade", TTR("The distance fade effect fades out each pixel based on its distance to another object."), {}, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ProximityFade", "Utility", "VisualShaderNodeProximityFade", TTR("The proximity fade effect fades out each pixel based on its distance to another object."), {}, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("RandomRange", "Utility", "VisualShaderNodeRandomRange", TTR("Returns a random value between the minimum and maximum input values."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Remap", "Utility", "VisualShaderNodeRemap", TTR("Remaps a given input from the input range to the output range."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); // VECTOR - add_options.push_back(AddOption("VectorFunc", "Vector", "Common", "VisualShaderNodeVectorFunc", TTR("Vector function."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("VectorOp", "Vector", "Common", "VisualShaderNodeVectorOp", TTR("Vector operator."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - - add_options.push_back(AddOption("VectorCompose", "Vector", "Composition", "VisualShaderNodeVectorCompose", TTR("Composes vector from three scalars."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("VectorDecompose", "Vector", "Composition", "VisualShaderNodeVectorDecompose", TTR("Decomposes vector to three scalars."))); - - add_options.push_back(AddOption("Abs", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the absolute value of the parameter."), VisualShaderNodeVectorFunc::FUNC_ABS, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ACos", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-cosine of the parameter."), VisualShaderNodeVectorFunc::FUNC_ACOS, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ACosH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic cosine of the parameter."), VisualShaderNodeVectorFunc::FUNC_ACOSH, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ASin", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-sine of the parameter."), VisualShaderNodeVectorFunc::FUNC_ASIN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ASinH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic sine of the parameter."), VisualShaderNodeVectorFunc::FUNC_ASINH, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ATan", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-tangent of the parameter."), VisualShaderNodeVectorFunc::FUNC_ATAN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ATan2", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the arc-tangent of the parameters."), VisualShaderNodeVectorOp::OP_ATAN2, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ATanH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), VisualShaderNodeVectorFunc::FUNC_ATANH, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Ceil", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), VisualShaderNodeVectorFunc::FUNC_CEIL, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Clamp", "Vector", "Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), VisualShaderNodeClamp::OP_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Cos", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the cosine of the parameter."), VisualShaderNodeVectorFunc::FUNC_COS, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("CosH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic cosine of the parameter."), VisualShaderNodeVectorFunc::FUNC_COSH, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Cross", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Calculates the cross product of two vectors."), VisualShaderNodeVectorOp::OP_CROSS, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Degrees", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in radians to degrees."), VisualShaderNodeVectorFunc::FUNC_DEGREES, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Distance", "Vector", "Functions", "VisualShaderNodeVectorDistance", TTR("Returns the distance between two points."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Dot", "Vector", "Functions", "VisualShaderNodeDotProduct", TTR("Calculates the dot product of two vectors."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Exp", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-e Exponential."), VisualShaderNodeVectorFunc::FUNC_EXP, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Exp2", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 Exponential."), VisualShaderNodeVectorFunc::FUNC_EXP2, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("FaceForward", "Vector", "Functions", "VisualShaderNodeFaceForward", TTR("Returns the vector that points in the same direction as a reference vector. The function has three vector parameters : N, the vector to orient, I, the incident vector, and Nref, the reference vector. If the dot product of I and Nref is smaller than zero the return value is N. Otherwise -N is returned."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Floor", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer less than or equal to the parameter."), VisualShaderNodeVectorFunc::FUNC_FLOOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Fract", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), VisualShaderNodeVectorFunc::FUNC_FRAC, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("InverseSqrt", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse of the square root of the parameter."), VisualShaderNodeVectorFunc::FUNC_INVERSE_SQRT, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Length", "Vector", "Functions", "VisualShaderNodeVectorLen", TTR("Calculates the length of a vector."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Log", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Natural logarithm."), VisualShaderNodeVectorFunc::FUNC_LOG, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Log2", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 logarithm."), VisualShaderNodeVectorFunc::FUNC_LOG2, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Max", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the greater of two values."), VisualShaderNodeVectorOp::OP_MAX, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Min", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the lesser of two values."), VisualShaderNodeVectorOp::OP_MIN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Mix", "Vector", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors."), VisualShaderNodeMix::OP_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("MixS", "Vector", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors using scalar."), VisualShaderNodeMix::OP_TYPE_VECTOR_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("MultiplyAdd", "Vector", "Functions", "VisualShaderNodeMultiplyAdd", TTR("Performs a fused multiply-add operation (a * b + c) on vectors."), VisualShaderNodeMultiplyAdd::OP_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Negate", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the opposite value of the parameter."), VisualShaderNodeVectorFunc::FUNC_NEGATE, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Normalize", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Calculates the normalize product of vector."), VisualShaderNodeVectorFunc::FUNC_NORMALIZE, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("OneMinus", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("1.0 - vector"), VisualShaderNodeVectorFunc::FUNC_ONEMINUS, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Pow", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the value of the first parameter raised to the power of the second."), VisualShaderNodeVectorOp::OP_POW, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Radians", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in degrees to radians."), VisualShaderNodeVectorFunc::FUNC_RADIANS, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Reciprocal", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("1.0 / vector"), VisualShaderNodeVectorFunc::FUNC_RECIPROCAL, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Reflect", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the vector that points in the direction of reflection ( a : incident vector, b : normal vector )."), VisualShaderNodeVectorOp::OP_REFLECT, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Refract", "Vector", "Functions", "VisualShaderNodeVectorRefract", TTR("Returns the vector that points in the direction of refraction."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Round", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer to the parameter."), VisualShaderNodeVectorFunc::FUNC_ROUND, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("RoundEven", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest even integer to the parameter."), VisualShaderNodeVectorFunc::FUNC_ROUNDEVEN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Saturate", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Clamps the value between 0.0 and 1.0."), VisualShaderNodeVectorFunc::FUNC_SATURATE, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Sign", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Extracts the sign of the parameter."), VisualShaderNodeVectorFunc::FUNC_SIGN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Sin", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the sine of the parameter."), VisualShaderNodeVectorFunc::FUNC_SIN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("SinH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic sine of the parameter."), VisualShaderNodeVectorFunc::FUNC_SINH, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Sqrt", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the square root of the parameter."), VisualShaderNodeVectorFunc::FUNC_SQRT, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("SmoothStep", "Vector", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( vector(edge0), vector(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), VisualShaderNodeSmoothStep::OP_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("SmoothStepS", "Vector", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( scalar(edge0), scalar(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Step", "Vector", "Functions", "VisualShaderNodeStep", TTR("Step function( vector(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), VisualShaderNodeStep::OP_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("StepS", "Vector", "Functions", "VisualShaderNodeStep", TTR("Step function( scalar(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), VisualShaderNodeStep::OP_TYPE_VECTOR_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Tan", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the tangent of the parameter."), VisualShaderNodeVectorFunc::FUNC_TAN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("TanH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic tangent of the parameter."), VisualShaderNodeVectorFunc::FUNC_TANH, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Trunc", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the truncated value of the parameter."), VisualShaderNodeVectorFunc::FUNC_TRUNC, VisualShaderNode::PORT_TYPE_VECTOR)); - - add_options.push_back(AddOption("Add", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Adds vector to vector."), VisualShaderNodeVectorOp::OP_ADD, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Divide", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Divides vector by vector."), VisualShaderNodeVectorOp::OP_DIV, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Multiply", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Multiplies vector by vector."), VisualShaderNodeVectorOp::OP_MUL, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Remainder", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two vectors."), VisualShaderNodeVectorOp::OP_MOD, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Subtract", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Subtracts vector from vector."), VisualShaderNodeVectorOp::OP_SUB, VisualShaderNode::PORT_TYPE_VECTOR)); - - add_options.push_back(AddOption("VectorConstant", "Vector", "Variables", "VisualShaderNodeVec3Constant", TTR("Vector constant."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("VectorUniform", "Vector", "Variables", "VisualShaderNodeVec3Uniform", TTR("Vector uniform."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("VectorFunc", "Vector/Common", "VisualShaderNodeVectorFunc", TTR("Vector function."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("VectorOp", "Vector/Common", "VisualShaderNodeVectorOp", TTR("Vector operator."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("VectorCompose", "Vector/Common", "VisualShaderNodeVectorCompose", TTR("Composes vector from scalars."))); + add_options.push_back(AddOption("VectorDecompose", "Vector/Common", "VisualShaderNodeVectorDecompose", TTR("Decomposes vector to scalars."))); + + add_options.push_back(AddOption("Vector2Compose", "Vector/Composition", "VisualShaderNodeVectorCompose", TTR("Composes 2D vector from two scalars."), { VisualShaderNodeVectorCompose::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Vector2Decompose", "Vector/Composition", "VisualShaderNodeVectorDecompose", TTR("Decomposes 2D vector to two scalars."), { VisualShaderNodeVectorDecompose::OP_TYPE_VECTOR_2D })); + add_options.push_back(AddOption("Vector3Compose", "Vector/Composition", "VisualShaderNodeVectorCompose", TTR("Composes 3D vector from three scalars."), { VisualShaderNodeVectorCompose::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Vector3Decompose", "Vector/Composition", "VisualShaderNodeVectorDecompose", TTR("Decomposes 3D vector to three scalars."), { VisualShaderNodeVectorDecompose::OP_TYPE_VECTOR_3D })); + add_options.push_back(AddOption("Vector4Compose", "Vector/Composition", "VisualShaderNodeVectorCompose", TTR("Composes 4D vector from four scalars."), { VisualShaderNodeVectorCompose::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Vector4Decompose", "Vector/Composition", "VisualShaderNodeVectorDecompose", TTR("Decomposes 4D vector to four scalars."), { VisualShaderNodeVectorDecompose::OP_TYPE_VECTOR_4D })); + + add_options.push_back(AddOption("Abs", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the absolute value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ABS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Abs", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the absolute value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ABS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Abs", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the absolute value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ABS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ACos", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ACOS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ACos", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ACOS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ACos", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ACOS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ACosH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ACOSH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ACosH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ACOSH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ACosH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ACOSH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ASin", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ASIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ASin", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ASIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ASin", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ASIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ASinH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ASINH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ASinH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ASINH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ASinH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ASINH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ATan", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATAN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ATan", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATAN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ATan", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATAN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ATan2", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the arc-tangent of the parameters."), { VisualShaderNodeVectorOp::OP_ATAN2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ATan2", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the arc-tangent of the parameters."), { VisualShaderNodeVectorOp::OP_ATAN2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ATan2", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the arc-tangent of the parameters."), { VisualShaderNodeVectorOp::OP_ATAN2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ATanH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ATanH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ATanH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Ceil", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_CEIL }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Ceil", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_CEIL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Ceil", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_CEIL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Clamp", "Vector/Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), { VisualShaderNodeClamp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Clamp", "Vector/Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), { VisualShaderNodeClamp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Clamp", "Vector/Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), { VisualShaderNodeClamp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Cos", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_COS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Cos", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_COS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Cos", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_COS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("CosH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_COSH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("CosH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_COSH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("CosH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_COSH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Cross", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Calculates the cross product of two vectors."), { VisualShaderNodeVectorOp::OP_CROSS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Degrees", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in radians to degrees."), { VisualShaderNodeVectorFunc::FUNC_DEGREES, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Degrees", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in radians to degrees."), { VisualShaderNodeVectorFunc::FUNC_DEGREES, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Degrees", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in radians to degrees."), { VisualShaderNodeVectorFunc::FUNC_DEGREES, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("DFdX", "Vector/Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'x' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_X, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("DFdX", "Vector/Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'x' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_X, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("DFdX", "Vector/Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'x' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_X, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("DFdY", "Vector/Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'y' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_Y, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("DFdY", "Vector/Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'y' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_Y, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("DFdY", "Vector/Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'y' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_Y, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("Distance2D", "Vector/Functions", "VisualShaderNodeVectorDistance", TTR("Returns the distance between two points."), { VisualShaderNodeVectorDistance::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Distance3D", "Vector/Functions", "VisualShaderNodeVectorDistance", TTR("Returns the distance between two points."), { VisualShaderNodeVectorDistance::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Distance4D", "Vector/Functions", "VisualShaderNodeVectorDistance", TTR("Returns the distance between two points."), { VisualShaderNodeVectorDistance::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Dot", "Vector/Functions", "VisualShaderNodeDotProduct", TTR("Calculates the dot product of two vectors."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Exp", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Base-e Exponential."), { VisualShaderNodeVectorFunc::FUNC_EXP, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Exp", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Base-e Exponential."), { VisualShaderNodeVectorFunc::FUNC_EXP, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Exp", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Base-e Exponential."), { VisualShaderNodeVectorFunc::FUNC_EXP, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Exp2", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 Exponential."), { VisualShaderNodeVectorFunc::FUNC_EXP2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Exp2", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 Exponential."), { VisualShaderNodeVectorFunc::FUNC_EXP2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Exp2", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 Exponential."), { VisualShaderNodeVectorFunc::FUNC_EXP2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("FaceForward", "Vector/Functions", "VisualShaderNodeFaceForward", TTR("Returns the vector that points in the same direction as a reference vector. The function has three vector parameters : N, the vector to orient, I, the incident vector, and Nref, the reference vector. If the dot product of I and Nref is smaller than zero the return value is N. Otherwise -N is returned."), { VisualShaderNodeFaceForward::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("FaceForward", "Vector/Functions", "VisualShaderNodeFaceForward", TTR("Returns the vector that points in the same direction as a reference vector. The function has three vector parameters : N, the vector to orient, I, the incident vector, and Nref, the reference vector. If the dot product of I and Nref is smaller than zero the return value is N. Otherwise -N is returned."), { VisualShaderNodeFaceForward::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("FaceForward", "Vector/Functions", "VisualShaderNodeFaceForward", TTR("Returns the vector that points in the same direction as a reference vector. The function has three vector parameters : N, the vector to orient, I, the incident vector, and Nref, the reference vector. If the dot product of I and Nref is smaller than zero the return value is N. Otherwise -N is returned."), { VisualShaderNodeFaceForward::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Floor", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer less than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_FLOOR, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Floor", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer less than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_FLOOR, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Floor", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer less than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_FLOOR, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Fract", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeVectorFunc::FUNC_FRACT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Fract", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeVectorFunc::FUNC_FRACT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Fract", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeVectorFunc::FUNC_FRACT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Fresnel", "Vector/Functions", "VisualShaderNodeFresnel", TTR("Returns falloff based on the dot product of surface normal and view direction of camera (pass associated inputs to it)."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("InverseSqrt", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse of the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_INVERSE_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("InverseSqrt", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse of the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_INVERSE_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("InverseSqrt", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse of the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_INVERSE_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Length2D", "Vector/Functions", "VisualShaderNodeVectorLen", TTR("Calculates the length of a vector."), { VisualShaderNodeVectorLen::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Length3D", "Vector/Functions", "VisualShaderNodeVectorLen", TTR("Calculates the length of a vector."), { VisualShaderNodeVectorLen::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Length4D", "Vector/Functions", "VisualShaderNodeVectorLen", TTR("Calculates the length of a vector."), { VisualShaderNodeVectorLen::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Log", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Natural logarithm."), { VisualShaderNodeVectorFunc::FUNC_LOG, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Log", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Natural logarithm."), { VisualShaderNodeVectorFunc::FUNC_LOG, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Log", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Natural logarithm."), { VisualShaderNodeVectorFunc::FUNC_LOG, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Log2", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 logarithm."), { VisualShaderNodeVectorFunc::FUNC_LOG2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Log2", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 logarithm."), { VisualShaderNodeVectorFunc::FUNC_LOG2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Log2", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 logarithm."), { VisualShaderNodeVectorFunc::FUNC_LOG2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Max", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the greater of two values."), { VisualShaderNodeVectorOp::OP_MAX, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Max", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the greater of two values."), { VisualShaderNodeVectorOp::OP_MAX, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Max", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the greater of two values."), { VisualShaderNodeVectorOp::OP_MAX, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Min", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the lesser of two values."), { VisualShaderNodeVectorOp::OP_MIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Min", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the lesser of two values."), { VisualShaderNodeVectorOp::OP_MIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Min", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the lesser of two values."), { VisualShaderNodeVectorOp::OP_MIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Mix", "Vector/Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors."), { VisualShaderNodeMix::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Mix", "Vector/Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors."), { VisualShaderNodeMix::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Mix", "Vector/Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors."), { VisualShaderNodeMix::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("MixS", "Vector/Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors using scalar."), { VisualShaderNodeMix::OP_TYPE_VECTOR_2D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("MixS", "Vector/Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors using scalar."), { VisualShaderNodeMix::OP_TYPE_VECTOR_3D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("MixS", "Vector/Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors using scalar."), { VisualShaderNodeMix::OP_TYPE_VECTOR_4D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("MultiplyAdd (a * b + c)", "Vector/Functions", "VisualShaderNodeMultiplyAdd", TTR("Performs a fused multiply-add operation (a * b + c) on vectors."), { VisualShaderNodeMultiplyAdd::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("MultiplyAdd (a * b + c)", "Vector/Functions", "VisualShaderNodeMultiplyAdd", TTR("Performs a fused multiply-add operation (a * b + c) on vectors."), { VisualShaderNodeMultiplyAdd::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("MultiplyAdd (a * b + c)", "Vector/Functions", "VisualShaderNodeMultiplyAdd", TTR("Performs a fused multiply-add operation (a * b + c) on vectors."), { VisualShaderNodeMultiplyAdd::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Negate (*-1)", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the opposite value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_NEGATE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Negate (*-1)", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the opposite value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_NEGATE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Negate (*-1)", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the opposite value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_NEGATE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Normalize", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Calculates the normalize product of vector."), { VisualShaderNodeVectorFunc::FUNC_NORMALIZE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Normalize", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Calculates the normalize product of vector."), { VisualShaderNodeVectorFunc::FUNC_NORMALIZE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Normalize", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Calculates the normalize product of vector."), { VisualShaderNodeVectorFunc::FUNC_NORMALIZE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("OneMinus (1-)", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("1.0 - vector"), { VisualShaderNodeVectorFunc::FUNC_ONEMINUS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("OneMinus (1-)", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("1.0 - vector"), { VisualShaderNodeVectorFunc::FUNC_ONEMINUS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("OneMinus (1-)", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("1.0 - vector"), { VisualShaderNodeVectorFunc::FUNC_ONEMINUS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Pow (^)", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the value of the first parameter raised to the power of the second."), { VisualShaderNodeVectorOp::OP_POW, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Pow (^)", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the value of the first parameter raised to the power of the second."), { VisualShaderNodeVectorOp::OP_POW, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Pow (^)", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the value of the first parameter raised to the power of the second."), { VisualShaderNodeVectorOp::OP_POW, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Radians", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in degrees to radians."), { VisualShaderNodeVectorFunc::FUNC_RADIANS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Radians", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in degrees to radians."), { VisualShaderNodeVectorFunc::FUNC_RADIANS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Radians", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in degrees to radians."), { VisualShaderNodeVectorFunc::FUNC_RADIANS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Reciprocal", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("1.0 / vector"), { VisualShaderNodeVectorFunc::FUNC_RECIPROCAL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Reciprocal", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("1.0 / vector"), { VisualShaderNodeVectorFunc::FUNC_RECIPROCAL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Reciprocal", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("1.0 / vector"), { VisualShaderNodeVectorFunc::FUNC_RECIPROCAL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Reflect", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the vector that points in the direction of reflection ( a : incident vector, b : normal vector )."), { VisualShaderNodeVectorOp::OP_REFLECT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Reflect", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the vector that points in the direction of reflection ( a : incident vector, b : normal vector )."), { VisualShaderNodeVectorOp::OP_REFLECT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Reflect", "Vector/Functions", "VisualShaderNodeVectorOp", TTR("Returns the vector that points in the direction of reflection ( a : incident vector, b : normal vector )."), { VisualShaderNodeVectorOp::OP_REFLECT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Refract", "Vector/Functions", "VisualShaderNodeVectorRefract", TTR("Returns the vector that points in the direction of refraction."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Refract", "Vector/Functions", "VisualShaderNodeVectorRefract", TTR("Returns the vector that points in the direction of refraction."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Refract", "Vector/Functions", "VisualShaderNodeVectorRefract", TTR("Returns the vector that points in the direction of refraction."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Round", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer to the parameter."), { VisualShaderNodeVectorFunc::FUNC_ROUND, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Round", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer to the parameter."), { VisualShaderNodeVectorFunc::FUNC_ROUND, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Round", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer to the parameter."), { VisualShaderNodeVectorFunc::FUNC_ROUND, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("RoundEven", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest even integer to the parameter."), { VisualShaderNodeVectorFunc::FUNC_ROUNDEVEN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("RoundEven", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest even integer to the parameter."), { VisualShaderNodeVectorFunc::FUNC_ROUNDEVEN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("RoundEven", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest even integer to the parameter."), { VisualShaderNodeVectorFunc::FUNC_ROUNDEVEN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Saturate", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Clamps the value between 0.0 and 1.0."), { VisualShaderNodeVectorFunc::FUNC_SATURATE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Saturate", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Clamps the value between 0.0 and 1.0."), { VisualShaderNodeVectorFunc::FUNC_SATURATE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Saturate", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Clamps the value between 0.0 and 1.0."), { VisualShaderNodeVectorFunc::FUNC_SATURATE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Sign", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Extracts the sign of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SIGN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Sign", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Extracts the sign of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SIGN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Sign", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Extracts the sign of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SIGN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Sin", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Sin", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Sin", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("SinH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SINH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("SinH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SINH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("SinH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SINH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Sqrt", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Sqrt", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Sqrt", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("SmoothStep", "Vector/Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( vector(edge0), vector(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("SmoothStep", "Vector/Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( vector(edge0), vector(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("SmoothStep", "Vector/Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( vector(edge0), vector(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("SmoothStepS", "Vector/Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( scalar(edge0), scalar(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_2D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("SmoothStepS", "Vector/Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( scalar(edge0), scalar(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_3D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("SmoothStepS", "Vector/Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( scalar(edge0), scalar(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_4D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Step", "Vector/Functions", "VisualShaderNodeStep", TTR("Step function( vector(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), { VisualShaderNodeStep::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Step", "Vector/Functions", "VisualShaderNodeStep", TTR("Step function( vector(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), { VisualShaderNodeStep::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("StepS", "Vector/Functions", "VisualShaderNodeStep", TTR("Step function( scalar(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), { VisualShaderNodeStep::OP_TYPE_VECTOR_2D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("StepS", "Vector/Functions", "VisualShaderNodeStep", TTR("Step function( scalar(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), { VisualShaderNodeStep::OP_TYPE_VECTOR_3D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("StepS", "Vector/Functions", "VisualShaderNodeStep", TTR("Step function( scalar(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), { VisualShaderNodeStep::OP_TYPE_VECTOR_4D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Sum (+)", "Vector/Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Sum of absolute derivative in 'x' and 'y'."), { VisualShaderNodeDerivativeFunc::FUNC_SUM, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("Sum (+)", "Vector/Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Sum of absolute derivative in 'x' and 'y'."), { VisualShaderNodeDerivativeFunc::FUNC_SUM, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("Sum (+)", "Vector/Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Sum of absolute derivative in 'x' and 'y'."), { VisualShaderNodeDerivativeFunc::FUNC_SUM, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("Tan", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TAN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Tan", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TAN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Tan", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TAN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("TanH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("TanH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("TanH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Trunc", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the truncated value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TRUNC, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Trunc", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the truncated value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TRUNC, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Trunc", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the truncated value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TRUNC, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + + add_options.push_back(AddOption("Add (+)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Adds 2D vector to 2D vector."), { VisualShaderNodeVectorOp::OP_ADD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Add (+)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Adds 3D vector to 3D vector."), { VisualShaderNodeVectorOp::OP_ADD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Add (+)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Adds 4D vector to 4D vector."), { VisualShaderNodeVectorOp::OP_ADD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Divide (/)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Divides 2D vector by 2D vector."), { VisualShaderNodeVectorOp::OP_DIV, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Divide (/)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Divides 3D vector by 3D vector."), { VisualShaderNodeVectorOp::OP_DIV, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Divide (/)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Divides 4D vector by 4D vector."), { VisualShaderNodeVectorOp::OP_DIV, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Multiply (*)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Multiplies 2D vector by 2D vector."), { VisualShaderNodeVectorOp::OP_MUL, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Multiply (*)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Multiplies 3D vector by 3D vector."), { VisualShaderNodeVectorOp::OP_MUL, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Multiply (*)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Multiplies 4D vector by 4D vector."), { VisualShaderNodeVectorOp::OP_MUL, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Remainder", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two 2D vectors."), { VisualShaderNodeVectorOp::OP_MOD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Remainder", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two 3D vectors."), { VisualShaderNodeVectorOp::OP_MOD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Remainder", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two 4D vectors."), { VisualShaderNodeVectorOp::OP_MOD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Subtract (-)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Subtracts 2D vector from 2D vector."), { VisualShaderNodeVectorOp::OP_SUB, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Subtract (-)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Subtracts 3D vector from 3D vector."), { VisualShaderNodeVectorOp::OP_SUB, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Subtract (-)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Subtracts 4D vector from 4D vector."), { VisualShaderNodeVectorOp::OP_SUB, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + + add_options.push_back(AddOption("Vector2Constant", "Vector/Variables", "VisualShaderNodeVec2Constant", TTR("2D vector constant."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Vector2Parameter", "Vector/Variables", "VisualShaderNodeVec2Parameter", TTR("2D vector parameter."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Vector3Constant", "Vector/Variables", "VisualShaderNodeVec3Constant", TTR("3D vector constant."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Vector3Parameter", "Vector/Variables", "VisualShaderNodeVec3Parameter", TTR("3D vector parameter."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Vector4Constant", "Vector/Variables", "VisualShaderNodeVec4Constant", TTR("4D vector constant."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Vector4Parameter", "Vector/Variables", "VisualShaderNodeVec4Parameter", TTR("4D vector parameter."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); // SPECIAL - add_options.push_back(AddOption("Comment", "Special", "", "VisualShaderNodeComment", TTR("A rectangular area with a description string for better graph organization."))); - add_options.push_back(AddOption("Expression", "Special", "", "VisualShaderNodeExpression", TTR("Custom Godot Shader Language expression, with custom amount of input and output ports. This is a direct injection of code into the vertex/fragment/light function, do not use it to write the function declarations inside."))); - add_options.push_back(AddOption("Fresnel", "Special", "", "VisualShaderNodeFresnel", TTR("Returns falloff based on the dot product of surface normal and view direction of camera (pass associated inputs to it)."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("GlobalExpression", "Special", "", "VisualShaderNodeGlobalExpression", TTR("Custom Godot Shader Language expression, which is placed on top of the resulted shader. You can place various function definitions inside and call it later in the Expressions. You can also declare varyings, uniforms and constants."))); - add_options.push_back(AddOption("UniformRef", "Special", "", "VisualShaderNodeUniformRef", TTR("A reference to an existing uniform."))); - - add_options.push_back(AddOption("ScalarDerivativeFunc", "Special", "Common", "VisualShaderNodeScalarDerivativeFunc", TTR("(Fragment/Light mode only) Scalar derivative function."), -1, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - add_options.push_back(AddOption("VectorDerivativeFunc", "Special", "Common", "VisualShaderNodeVectorDerivativeFunc", TTR("(Fragment/Light mode only) Vector derivative function."), -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - - add_options.push_back(AddOption("DdX", "Special", "Derivative", "VisualShaderNodeVectorDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'x' using local differencing."), VisualShaderNodeVectorDerivativeFunc::FUNC_X, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - add_options.push_back(AddOption("DdXS", "Special", "Derivative", "VisualShaderNodeScalarDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Derivative in 'x' using local differencing."), VisualShaderNodeScalarDerivativeFunc::FUNC_X, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - add_options.push_back(AddOption("DdY", "Special", "Derivative", "VisualShaderNodeVectorDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'y' using local differencing."), VisualShaderNodeVectorDerivativeFunc::FUNC_Y, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - add_options.push_back(AddOption("DdYS", "Special", "Derivative", "VisualShaderNodeScalarDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Derivative in 'y' using local differencing."), VisualShaderNodeScalarDerivativeFunc::FUNC_Y, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - add_options.push_back(AddOption("Sum", "Special", "Derivative", "VisualShaderNodeVectorDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Sum of absolute derivative in 'x' and 'y'."), VisualShaderNodeVectorDerivativeFunc::FUNC_SUM, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - add_options.push_back(AddOption("SumS", "Special", "Derivative", "VisualShaderNodeScalarDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Sum of absolute derivative in 'x' and 'y'."), VisualShaderNodeScalarDerivativeFunc::FUNC_SUM, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); + add_options.push_back(AddOption("Comment", "Special", "VisualShaderNodeComment", TTR("A rectangular area with a description string for better graph organization."))); + add_options.push_back(AddOption("Expression", "Special", "VisualShaderNodeExpression", TTR("Custom Godot Shader Language expression, with custom amount of input and output ports. This is a direct injection of code into the vertex/fragment/light function, do not use it to write the function declarations inside."))); + add_options.push_back(AddOption("GlobalExpression", "Special", "VisualShaderNodeGlobalExpression", TTR("Custom Godot Shader Language expression, which is placed on top of the resulted shader. You can place various function definitions inside and call it later in the Expressions. You can also declare varyings, parameters and constants."))); + add_options.push_back(AddOption("ParameterRef", "Special", "VisualShaderNodeParameterRef", TTR("A reference to an existing parameter."))); + add_options.push_back(AddOption("VaryingGetter", "Special", "VisualShaderNodeVaryingGetter", TTR("Get varying parameter."), {}, -1, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("VaryingSetter", "Special", "VisualShaderNodeVaryingSetter", TTR("Set varying parameter."), {}, -1, TYPE_FLAGS_VERTEX | TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("VaryingGetter", "Special", "VisualShaderNodeVaryingGetter", TTR("Get varying parameter."), {}, -1, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("VaryingSetter", "Special", "VisualShaderNodeVaryingSetter", TTR("Set varying parameter."), {}, -1, TYPE_FLAGS_VERTEX | TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + custom_node_option_idx = add_options.size(); ///////////////////////////////////////////////////////////////////// _update_options_menu(); - undo_redo = EditorNode::get_singleton()->get_undo_redo(); - Ref<VisualShaderNodePluginDefault> default_plugin; default_plugin.instantiate(); + default_plugin->set_editor(this); add_plugin(default_plugin); graph_plugin.instantiate(); + graph_plugin->set_editor(this); - property_editor = memnew(CustomPropertyEditor); - add_child(property_editor); + property_editor_popup = memnew(PopupPanel); + property_editor_popup->set_min_size(Size2(180, 0) * EDSCALE); + add_child(property_editor_popup); - property_editor->connect("variant_changed", callable_mp(this, &VisualShaderEditor::_port_edited)); + edited_property_holder.instantiate(); } -///////////////// - -void VisualShaderEditorPlugin::edit(Object *p_object) { - visual_shader_editor->edit(Object::cast_to<VisualShader>(p_object)); -} +class VisualShaderNodePluginInputEditor : public OptionButton { + GDCLASS(VisualShaderNodePluginInputEditor, OptionButton); -bool VisualShaderEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("VisualShader"); -} + VisualShaderEditor *editor = nullptr; + Ref<VisualShaderNodeInput> input; -void VisualShaderEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - //editor->hide_animation_player_editors(); - //editor->animation_panel_make_visible(true); - button->show(); - editor->make_bottom_panel_item_visible(visual_shader_editor); - visual_shader_editor->update_custom_nodes(); - visual_shader_editor->set_process_input(true); - //visual_shader_editor->set_process(true); - } else { - if (visual_shader_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); +public: + void _notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: { + connect("item_selected", callable_mp(this, &VisualShaderNodePluginInputEditor::_item_selected)); + } break; } - button->hide(); - visual_shader_editor->set_process_input(false); - //visual_shader_editor->set_process(false); } -} -VisualShaderEditorPlugin::VisualShaderEditorPlugin(EditorNode *p_node) { - editor = p_node; - visual_shader_editor = memnew(VisualShaderEditor); - visual_shader_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE); + void _item_selected(int p_item) { + editor->call_deferred(SNAME("_input_select_item"), input, get_item_text(p_item)); + } - button = editor->add_bottom_panel_item(TTR("VisualShader"), visual_shader_editor); - button->hide(); -} + void setup(VisualShaderEditor *p_editor, const Ref<VisualShaderNodeInput> &p_input) { + editor = p_editor; + input = p_input; + Ref<Texture2D> type_icon[] = { + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("float"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("int"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("uint"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector4"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("bool"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ImageTexture"), SNAME("EditorIcons")), + }; -VisualShaderEditorPlugin::~VisualShaderEditorPlugin() { -} + add_item("[None]"); + int to_select = -1; + for (int i = 0; i < input->get_input_index_count(); i++) { + if (input->get_input_name() == input->get_input_index_name(i)) { + to_select = i + 1; + } + add_icon_item(type_icon[input->get_input_index_type(i)], input->get_input_index_name(i)); + } + + if (to_select >= 0) { + select(to_select); + } + } +}; //////////////// -class VisualShaderNodePluginInputEditor : public OptionButton { - GDCLASS(VisualShaderNodePluginInputEditor, OptionButton); +class VisualShaderNodePluginVaryingEditor : public OptionButton { + GDCLASS(VisualShaderNodePluginVaryingEditor, OptionButton); - Ref<VisualShaderNodeInput> input; + VisualShaderEditor *editor = nullptr; + Ref<VisualShaderNodeVarying> varying; public: void _notification(int p_what) { if (p_what == NOTIFICATION_READY) { - connect("item_selected", callable_mp(this, &VisualShaderNodePluginInputEditor::_item_selected)); + connect("item_selected", callable_mp(this, &VisualShaderNodePluginVaryingEditor::_item_selected)); } } void _item_selected(int p_item) { - VisualShaderEditor::get_singleton()->call_deferred(SNAME("_input_select_item"), input, get_item_text(p_item)); + editor->call_deferred(SNAME("_varying_select_item"), varying, get_item_text(p_item)); } - void setup(const Ref<VisualShaderNodeInput> &p_input) { - input = p_input; - Ref<Texture2D> type_icon[6] = { + void setup(VisualShaderEditor *p_editor, const Ref<VisualShaderNodeVarying> &p_varying, VisualShader::Type p_type) { + editor = p_editor; + varying = p_varying; + + Ref<Texture2D> type_icon[] = { EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("float"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("int"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("uint"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector4"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("bool"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")), - EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ImageTexture"), SNAME("EditorIcons")), }; + bool is_getter = Ref<VisualShaderNodeVaryingGetter>(p_varying.ptr()).is_valid(); + add_item("[None]"); + int to_select = -1; - for (int i = 0; i < input->get_input_index_count(); i++) { - if (input->get_input_name() == input->get_input_index_name(i)) { - to_select = i + 1; + for (int i = 0, j = 0; i < varying->get_varyings_count(); i++) { + VisualShader::VaryingMode mode = varying->get_varying_mode_by_index(i); + if (is_getter) { + if (mode == VisualShader::VARYING_MODE_FRAG_TO_LIGHT) { + if (p_type != VisualShader::TYPE_LIGHT) { + j++; + continue; + } + } else { + if (p_type != VisualShader::TYPE_FRAGMENT && p_type != VisualShader::TYPE_LIGHT) { + j++; + continue; + } + } + } else { + if (mode == VisualShader::VARYING_MODE_FRAG_TO_LIGHT) { + if (p_type != VisualShader::TYPE_FRAGMENT) { + j++; + continue; + } + } else { + if (p_type != VisualShader::TYPE_VERTEX) { + j++; + continue; + } + } } - add_icon_item(type_icon[input->get_input_index_type(i)], input->get_input_index_name(i)); + if (varying->get_varying_name() == varying->get_varying_name_by_index(i)) { + to_select = i - j + 1; + } + add_icon_item(type_icon[varying->get_varying_type_by_index(i)], varying->get_varying_name_by_index(i)); } if (to_select >= 0) { @@ -4867,30 +6253,37 @@ public: //////////////// -class VisualShaderNodePluginUniformRefEditor : public OptionButton { - GDCLASS(VisualShaderNodePluginUniformRefEditor, OptionButton); +class VisualShaderNodePluginParameterRefEditor : public OptionButton { + GDCLASS(VisualShaderNodePluginParameterRefEditor, OptionButton); - Ref<VisualShaderNodeUniformRef> uniform_ref; + VisualShaderEditor *editor = nullptr; + Ref<VisualShaderNodeParameterRef> parameter_ref; public: void _notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - connect("item_selected", callable_mp(this, &VisualShaderNodePluginUniformRefEditor::_item_selected)); + switch (p_what) { + case NOTIFICATION_READY: { + connect("item_selected", callable_mp(this, &VisualShaderNodePluginParameterRefEditor::_item_selected)); + } break; } } void _item_selected(int p_item) { - VisualShaderEditor::get_singleton()->call_deferred(SNAME("_uniform_select_item"), uniform_ref, get_item_text(p_item)); + editor->call_deferred(SNAME("_parameter_ref_select_item"), parameter_ref, get_item_text(p_item)); } - void setup(const Ref<VisualShaderNodeUniformRef> &p_uniform_ref) { - uniform_ref = p_uniform_ref; + void setup(VisualShaderEditor *p_editor, const Ref<VisualShaderNodeParameterRef> &p_parameter_ref) { + editor = p_editor; + parameter_ref = p_parameter_ref; - Ref<Texture2D> type_icon[7] = { + Ref<Texture2D> type_icon[] = { EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("float"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("int"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("uint"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("bool"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector4"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Color"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ImageTexture"), SNAME("EditorIcons")), @@ -4898,11 +6291,11 @@ public: add_item("[None]"); int to_select = -1; - for (int i = 0; i < p_uniform_ref->get_uniforms_count(); i++) { - if (p_uniform_ref->get_uniform_name() == p_uniform_ref->get_uniform_name_by_index(i)) { + for (int i = 0; i < p_parameter_ref->get_parameters_count(); i++) { + if (p_parameter_ref->get_parameter_name() == p_parameter_ref->get_parameter_name_by_index(i)) { to_select = i + 1; } - add_icon_item(type_icon[p_uniform_ref->get_uniform_type_by_index(i)], p_uniform_ref->get_uniform_name_by_index(i)); + add_icon_item(type_icon[p_parameter_ref->get_parameter_type_by_index(i)], p_parameter_ref->get_parameter_name_by_index(i)); } if (to_select >= 0) { @@ -4915,8 +6308,9 @@ public: class VisualShaderNodePluginDefaultEditor : public VBoxContainer { GDCLASS(VisualShaderNodePluginDefaultEditor, VBoxContainer); + VisualShaderEditor *editor = nullptr; Ref<Resource> parent_resource; - int node_id; + int node_id = 0; VisualShader::Type shader_type; public: @@ -4925,7 +6319,7 @@ public: return; } - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); updating = true; undo_redo->create_action(TTR("Edit Visual Property:") + " " + p_property, UndoRedo::MERGE_ENDS); @@ -4933,23 +6327,28 @@ public: undo_redo->add_undo_property(node.ptr(), p_property, node->get(p_property)); if (p_value.get_type() == Variant::OBJECT) { - RES prev_res = node->get(p_property); - RES curr_res = p_value; + Ref<Resource> prev_res = node->get(p_property); + Ref<Resource> curr_res = p_value; if (curr_res.is_null()) { - undo_redo->add_do_method(this, "_open_inspector", (RES)parent_resource.ptr()); + undo_redo->add_do_method(this, "_open_inspector", (Ref<Resource>)parent_resource.ptr()); } else { - undo_redo->add_do_method(this, "_open_inspector", (RES)curr_res.ptr()); + undo_redo->add_do_method(this, "_open_inspector", (Ref<Resource>)curr_res.ptr()); } if (!prev_res.is_null()) { - undo_redo->add_undo_method(this, "_open_inspector", (RES)prev_res.ptr()); + undo_redo->add_undo_method(this, "_open_inspector", (Ref<Resource>)prev_res.ptr()); } else { - undo_redo->add_undo_method(this, "_open_inspector", (RES)parent_resource.ptr()); + undo_redo->add_undo_method(this, "_open_inspector", (Ref<Resource>)parent_resource.ptr()); } } if (p_property != "constant") { - undo_redo->add_do_method(VisualShaderEditor::get_singleton()->get_graph_plugin(), "update_node_deferred", shader_type, node_id); - undo_redo->add_undo_method(VisualShaderEditor::get_singleton()->get_graph_plugin(), "update_node_deferred", shader_type, node_id); + VisualShaderGraphPlugin *graph_plugin = editor->get_graph_plugin(); + if (graph_plugin) { + undo_redo->add_do_method(editor, "_update_next_previews", node_id); + undo_redo->add_undo_method(editor, "_update_next_previews", node_id); + undo_redo->add_do_method(graph_plugin, "update_node_deferred", shader_type, node_id); + undo_redo->add_undo_method(graph_plugin, "update_node_deferred", shader_type, node_id); + } } undo_redo->commit_action(); @@ -4965,15 +6364,15 @@ public: } } - void _resource_selected(const String &p_path, RES p_resource) { + void _resource_selected(const String &p_path, Ref<Resource> p_resource) { _open_inspector(p_resource); } - void _open_inspector(RES p_resource) { - EditorNode::get_singleton()->get_inspector()->edit(p_resource.ptr()); + void _open_inspector(Ref<Resource> p_resource) { + InspectorDock::get_inspector_singleton()->edit(p_resource.ptr()); } - bool updating; + bool updating = false; Ref<VisualShaderNode> node; Vector<EditorProperty *> properties; Vector<Label *> prop_names; @@ -4984,7 +6383,8 @@ public: } } - void setup(Ref<Resource> p_parent_resource, Vector<EditorProperty *> p_properties, const Vector<StringName> &p_names, const Map<StringName, String> &p_overrided_names, Ref<VisualShaderNode> p_node) { + void setup(VisualShaderEditor *p_editor, Ref<Resource> p_parent_resource, Vector<EditorProperty *> p_properties, const Vector<StringName> &p_names, const HashMap<StringName, String> &p_overrided_names, Ref<VisualShaderNode> p_node) { + editor = p_editor; parent_resource = p_parent_resource; updating = false; node = p_node; @@ -5033,18 +6433,24 @@ public: }; Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node) { - if (p_node->is_class("VisualShaderNodeUniformRef")) { - //create input - VisualShaderNodePluginUniformRefEditor *uniform_editor = memnew(VisualShaderNodePluginUniformRefEditor); - uniform_editor->setup(p_node); - return uniform_editor; + Ref<VisualShader> p_shader = Ref<VisualShader>(p_parent_resource.ptr()); + + if (p_shader.is_valid() && (p_node->is_class("VisualShaderNodeVaryingGetter") || p_node->is_class("VisualShaderNodeVaryingSetter"))) { + VisualShaderNodePluginVaryingEditor *editor = memnew(VisualShaderNodePluginVaryingEditor); + editor->setup(vseditor, p_node, p_shader->get_shader_type()); + return editor; + } + + if (p_node->is_class("VisualShaderNodeParameterRef")) { + VisualShaderNodePluginParameterRefEditor *editor = memnew(VisualShaderNodePluginParameterRefEditor); + editor->setup(vseditor, p_node); + return editor; } if (p_node->is_class("VisualShaderNodeInput")) { - //create input - VisualShaderNodePluginInputEditor *input_editor = memnew(VisualShaderNodePluginInputEditor); - input_editor->setup(p_node); - return input_editor; + VisualShaderNodePluginInputEditor *editor = memnew(VisualShaderNodePluginInputEditor); + editor->setup(vseditor, p_node); + return editor; } Vector<StringName> properties = p_node->get_editable_properties(); @@ -5085,6 +6491,8 @@ Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_par prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); } else if (Object::cast_to<EditorPropertyTransform3D>(prop) || Object::cast_to<EditorPropertyVector3>(prop)) { prop->set_custom_minimum_size(Size2(250 * EDSCALE, 0)); + } else if (Object::cast_to<EditorPropertyQuaternion>(prop)) { + prop->set_custom_minimum_size(Size2(320 * EDSCALE, 0)); } else if (Object::cast_to<EditorPropertyFloat>(prop)) { prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); } else if (Object::cast_to<EditorPropertyEnum>(prop)) { @@ -5096,28 +6504,33 @@ Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_par properties.push_back(pinfo[i].name); } VisualShaderNodePluginDefaultEditor *editor = memnew(VisualShaderNodePluginDefaultEditor); - editor->setup(p_parent_resource, editors, properties, p_node->get_editable_properties_names(), p_node); + editor->setup(vseditor, p_parent_resource, editors, properties, p_node->get_editable_properties_names(), p_node); return editor; } -void EditorPropertyShaderMode::_option_selected(int p_which) { - //will not use this, instead will do all the logic setting manually - //emit_signal(SNAME("property_changed"), get_edited_property(), p_which); - +void EditorPropertyVisualShaderMode::_option_selected(int p_which) { Ref<VisualShader> visual_shader(Object::cast_to<VisualShader>(get_edited_object())); - if (visual_shader->get_mode() == p_which) { return; } - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + ShaderEditorPlugin *shader_editor = Object::cast_to<ShaderEditorPlugin>(EditorNode::get_singleton()->get_editor_data().get_editor("Shader")); + if (!shader_editor) { + return; + } + VisualShaderEditor *editor = shader_editor->get_visual_shader_editor(visual_shader); + if (!editor) { + return; + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Visual Shader Mode Changed")); //do is easy undo_redo->add_do_method(visual_shader.ptr(), "set_mode", p_which); undo_redo->add_undo_method(visual_shader.ptr(), "set_mode", visual_shader->get_mode()); - undo_redo->add_do_method(VisualShaderEditor::get_singleton(), "_set_mode", p_which); - undo_redo->add_undo_method(VisualShaderEditor::get_singleton(), "_set_mode", visual_shader->get_mode()); + undo_redo->add_do_method(editor, "_set_mode", p_which); + undo_redo->add_undo_method(editor, "_set_mode", visual_shader->get_mode()); //now undo is hell @@ -5156,52 +6569,67 @@ void EditorPropertyShaderMode::_option_selected(int p_which) { } } - undo_redo->add_do_method(VisualShaderEditor::get_singleton(), "_update_options_menu"); - undo_redo->add_undo_method(VisualShaderEditor::get_singleton(), "_update_options_menu"); + //4. delete varyings (if needed) + if (p_which == VisualShader::MODE_PARTICLES || p_which == VisualShader::MODE_SKY || p_which == VisualShader::MODE_FOG) { + int var_count = visual_shader->get_varyings_count(); + + if (var_count > 0) { + for (int i = 0; i < var_count; i++) { + const VisualShader::Varying *var = visual_shader->get_varying_by_index(i); + undo_redo->add_do_method(visual_shader.ptr(), "remove_varying", var->name); + undo_redo->add_undo_method(visual_shader.ptr(), "add_varying", var->name, var->mode, var->type); + } + + undo_redo->add_do_method(editor, "_update_varyings"); + undo_redo->add_undo_method(editor, "_update_varyings"); + } + } + + undo_redo->add_do_method(editor, "_update_nodes"); + undo_redo->add_undo_method(editor, "_update_nodes"); - //update graph - undo_redo->add_do_method(VisualShaderEditor::get_singleton(), "_update_graph"); - undo_redo->add_undo_method(VisualShaderEditor::get_singleton(), "_update_graph"); + undo_redo->add_do_method(editor, "_update_graph"); + undo_redo->add_undo_method(editor, "_update_graph"); undo_redo->commit_action(); } -void EditorPropertyShaderMode::update_property() { +void EditorPropertyVisualShaderMode::update_property() { int which = get_edited_object()->get(get_edited_property()); options->select(which); } -void EditorPropertyShaderMode::setup(const Vector<String> &p_options) { +void EditorPropertyVisualShaderMode::setup(const Vector<String> &p_options) { for (int i = 0; i < p_options.size(); i++) { options->add_item(p_options[i], i); } } -void EditorPropertyShaderMode::set_option_button_clip(bool p_enable) { +void EditorPropertyVisualShaderMode::set_option_button_clip(bool p_enable) { options->set_clip_text(p_enable); } -void EditorPropertyShaderMode::_bind_methods() { +void EditorPropertyVisualShaderMode::_bind_methods() { } -EditorPropertyShaderMode::EditorPropertyShaderMode() { +EditorPropertyVisualShaderMode::EditorPropertyVisualShaderMode() { options = memnew(OptionButton); options->set_clip_text(true); add_child(options); add_focusable(options); - options->connect("item_selected", callable_mp(this, &EditorPropertyShaderMode::_option_selected)); + options->connect("item_selected", callable_mp(this, &EditorPropertyVisualShaderMode::_option_selected)); } -bool EditorInspectorShaderModePlugin::can_handle(Object *p_object) { +bool EditorInspectorVisualShaderModePlugin::can_handle(Object *p_object) { return true; // Can handle everything. } -bool EditorInspectorShaderModePlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { +bool EditorInspectorVisualShaderModePlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) { if (p_path == "mode" && p_object->is_class("VisualShader") && p_type == Variant::INT) { - EditorPropertyShaderMode *editor = memnew(EditorPropertyShaderMode); + EditorPropertyVisualShaderMode *mode_editor = memnew(EditorPropertyVisualShaderMode); Vector<String> options = p_hint_text.split(","); - editor->setup(options); - add_property_editor(p_path, editor); + mode_editor->setup(options); + add_property_editor(p_path, mode_editor); return true; } @@ -5212,7 +6640,7 @@ bool EditorInspectorShaderModePlugin::parse_property(Object *p_object, const Var ////////////////////////////////// void VisualShaderNodePortPreview::_shader_changed() { - if (shader.is_null()) { + if (!is_valid || shader.is_null()) { return; } @@ -5224,18 +6652,18 @@ void VisualShaderNodePortPreview::_shader_changed() { preview_shader->set_code(shader_code); for (int i = 0; i < default_textures.size(); i++) { for (int j = 0; j < default_textures[i].params.size(); j++) { - preview_shader->set_default_texture_param(default_textures[i].name, default_textures[i].params[j], j); + preview_shader->set_default_texture_parameter(default_textures[i].name, default_textures[i].params[j], j); } } - Ref<ShaderMaterial> material; - material.instantiate(); - material->set_shader(preview_shader); + Ref<ShaderMaterial> mat; + mat.instantiate(); + mat->set_shader(preview_shader); //find if a material is also being edited and copy parameters to this one - for (int i = EditorNode::get_singleton()->get_editor_history()->get_path_size() - 1; i >= 0; i--) { - Object *object = ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_history()->get_path_object(i)); + for (int i = EditorNode::get_singleton()->get_editor_selection_history()->get_path_size() - 1; i >= 0; i--) { + Object *object = ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_selection_history()->get_path_object(i)); ShaderMaterial *src_mat; if (!object) { continue; @@ -5249,49 +6677,68 @@ void VisualShaderNodePortPreview::_shader_changed() { } if (src_mat && src_mat->get_shader().is_valid()) { List<PropertyInfo> params; - src_mat->get_shader()->get_param_list(¶ms); + src_mat->get_shader()->get_shader_uniform_list(¶ms); for (const PropertyInfo &E : params) { - material->set(E.name, src_mat->get(E.name)); + mat->set(E.name, src_mat->get(E.name)); } } } - set_material(material); + set_material(mat); } -void VisualShaderNodePortPreview::setup(const Ref<VisualShader> &p_shader, VisualShader::Type p_type, int p_node, int p_port) { +void VisualShaderNodePortPreview::setup(const Ref<VisualShader> &p_shader, VisualShader::Type p_type, int p_node, int p_port, bool p_is_valid) { shader = p_shader; - shader->connect("changed", callable_mp(this, &VisualShaderNodePortPreview::_shader_changed)); + shader->connect("changed", callable_mp(this, &VisualShaderNodePortPreview::_shader_changed), CONNECT_DEFERRED); type = p_type; port = p_port; node = p_node; - update(); + is_valid = p_is_valid; + queue_redraw(); _shader_changed(); } Size2 VisualShaderNodePortPreview::get_minimum_size() const { - return Size2(100, 100) * EDSCALE; + int port_preview_size = EDITOR_GET("editors/visual_editors/visual_shader/port_preview_size"); + return Size2(port_preview_size, port_preview_size) * EDSCALE; } void VisualShaderNodePortPreview::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - Vector<Vector2> points; - Vector<Vector2> uvs; - Vector<Color> colors; - points.push_back(Vector2()); - uvs.push_back(Vector2(0, 0)); - colors.push_back(Color(1, 1, 1, 1)); - points.push_back(Vector2(get_size().width, 0)); - uvs.push_back(Vector2(1, 0)); - colors.push_back(Color(1, 1, 1, 1)); - points.push_back(get_size()); - uvs.push_back(Vector2(1, 1)); - colors.push_back(Color(1, 1, 1, 1)); - points.push_back(Vector2(0, get_size().height)); - uvs.push_back(Vector2(0, 1)); - colors.push_back(Color(1, 1, 1, 1)); - - draw_primitive(points, colors, uvs); + switch (p_what) { + case NOTIFICATION_DRAW: { + Vector<Vector2> points = { + Vector2(), + Vector2(get_size().width, 0), + get_size(), + Vector2(0, get_size().height) + }; + + Vector<Vector2> uvs = { + Vector2(0, 0), + Vector2(1, 0), + Vector2(1, 1), + Vector2(0, 1) + }; + + if (is_valid) { + Vector<Color> colors = { + Color(1, 1, 1, 1), + Color(1, 1, 1, 1), + Color(1, 1, 1, 1), + Color(1, 1, 1, 1) + }; + draw_primitive(points, colors, uvs); + } else { + Vector<Color> colors = { + Color(0, 0, 0, 1), + Color(0, 0, 0, 1), + Color(0, 0, 0, 1), + Color(0, 0, 0, 1) + }; + draw_primitive(points, colors, uvs); + } + + } break; } } diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index b68a8bac76..21139fbddd 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -1,55 +1,66 @@ -/*************************************************************************/ -/* visual_shader_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* visual_shader_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 VISUAL_SHADER_EDITOR_PLUGIN_H #define VISUAL_SHADER_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" -#include "editor/plugins/curve_editor_plugin.h" -#include "editor/property_editor.h" -#include "scene/gui/button.h" -#include "scene/gui/graph_edit.h" -#include "scene/gui/popup.h" -#include "scene/gui/tree.h" +#include "editor/editor_properties.h" +#include "editor/plugins/editor_resource_conversion_plugin.h" +#include "scene/resources/syntax_highlighter.h" #include "scene/resources/visual_shader.h" +class CodeEdit; +class CurveEditor; +class GraphEdit; +class GraphNode; +class MenuButton; +class PopupPanel; +class RichTextLabel; +class Tree; + +class VisualShaderEditor; + class VisualShaderNodePlugin : public RefCounted { GDCLASS(VisualShaderNodePlugin, RefCounted); protected: + VisualShaderEditor *vseditor = nullptr; + +protected: static void _bind_methods(); - GDVIRTUAL2RC(Object *, _create_editor, RES, Ref<VisualShaderNode>) + GDVIRTUAL2RC(Object *, _create_editor, Ref<Resource>, Ref<VisualShaderNode>) public: + void set_editor(VisualShaderEditor *p_editor); virtual Control *create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node); }; @@ -57,6 +68,8 @@ class VisualShaderGraphPlugin : public RefCounted { GDCLASS(VisualShaderGraphPlugin, RefCounted); private: + VisualShaderEditor *editor = nullptr; + struct InputPort { Button *default_input_button = nullptr; }; @@ -71,102 +84,135 @@ private: GraphNode *graph_node = nullptr; bool preview_visible = false; int preview_pos = 0; - Map<int, InputPort> input_ports; - Map<int, Port> output_ports; + HashMap<int, InputPort> input_ports; + HashMap<int, Port> output_ports; VBoxContainer *preview_box = nullptr; - LineEdit *uniform_name = nullptr; + LineEdit *parameter_name = nullptr; CodeEdit *expression_edit = nullptr; CurveEditor *curve_editors[3] = { nullptr, nullptr, nullptr }; }; Ref<VisualShader> visual_shader; - Map<int, Link> links; + HashMap<int, Link> links; List<VisualShader::Connection> connections; - bool dirty = false; - Color vector_expanded_color[3]; + Color vector_expanded_color[4]; protected: static void _bind_methods(); public: + void set_editor(VisualShaderEditor *p_editor); void register_shader(VisualShader *p_visual_shader); - void set_connections(List<VisualShader::Connection> &p_connections); + void set_connections(const List<VisualShader::Connection> &p_connections); void register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphNode *p_graph_node); void register_output_port(int p_id, int p_port, TextureButton *p_button); - void register_uniform_name(int p_id, LineEdit *p_uniform_name); + void register_parameter_name(int p_id, LineEdit *p_parameter_name); void register_default_input_button(int p_node_id, int p_port_id, Button *p_button); void register_expression_edit(int p_node_id, CodeEdit *p_expression_edit); void register_curve_editor(int p_node_id, int p_index, CurveEditor *p_curve_editor); void clear_links(); void set_shader_type(VisualShader::Type p_type); bool is_preview_visible(int p_id) const; - bool is_dirty() const; - void make_dirty(bool p_enabled); void update_node(VisualShader::Type p_type, int p_id); void update_node_deferred(VisualShader::Type p_type, int p_node_id); - void add_node(VisualShader::Type p_type, int p_id); - void remove_node(VisualShader::Type p_type, int p_id); + void add_node(VisualShader::Type p_type, int p_id, bool p_just_update); + void remove_node(VisualShader::Type p_type, int p_id, bool p_just_update); void connect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port); void disconnect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port); - void show_port_preview(VisualShader::Type p_type, int p_node_id, int p_port_id); + void show_port_preview(VisualShader::Type p_type, int p_node_id, int p_port_id, bool p_is_valid); void set_node_position(VisualShader::Type p_type, int p_id, const Vector2 &p_position); void refresh_node_ports(VisualShader::Type p_type, int p_node); void set_input_port_default_value(VisualShader::Type p_type, int p_node_id, int p_port_id, Variant p_value); - void update_uniform_refs(); - void set_uniform_name(VisualShader::Type p_type, int p_node_id, const String &p_name); + void update_parameter_refs(); + void set_parameter_name(VisualShader::Type p_type, int p_node_id, const String &p_name); void update_curve(int p_node_id); void update_curve_xyz(int p_node_id); void set_expression(VisualShader::Type p_type, int p_node_id, const String &p_expression); int get_constant_index(float p_constant) const; + Ref<Script> get_node_script(int p_node_id) const; void update_node_size(int p_node_id); void update_theme(); + bool is_node_has_parameter_instances_relatively(VisualShader::Type p_type, int p_node) const; VisualShader::Type get_shader_type() const; VisualShaderGraphPlugin(); ~VisualShaderGraphPlugin(); }; +class VisualShaderEditedProperty : public RefCounted { + GDCLASS(VisualShaderEditedProperty, RefCounted); + +private: + Variant edited_property; + +protected: + static void _bind_methods(); + +public: + void set_edited_property(Variant p_variant); + Variant get_edited_property() const; + + VisualShaderEditedProperty() {} +}; + class VisualShaderEditor : public VBoxContainer { GDCLASS(VisualShaderEditor, VBoxContainer); friend class VisualShaderGraphPlugin; - CustomPropertyEditor *property_editor; - int editing_node; - int editing_port; + PopupPanel *property_editor_popup = nullptr; + EditorProperty *property_editor = nullptr; + int editing_node = -1; + int editing_port = -1; + Ref<VisualShaderEditedProperty> edited_property_holder; Ref<VisualShader> visual_shader; - GraphEdit *graph; - Button *add_node; - Button *preview_shader; + GraphEdit *graph = nullptr; + Button *add_node = nullptr; + MenuButton *varying_button = nullptr; + Button *preview_shader = nullptr; OptionButton *edit_type = nullptr; - OptionButton *edit_type_standard; - OptionButton *edit_type_particles; - OptionButton *edit_type_sky; - OptionButton *edit_type_fog; - CheckBox *custom_mode_box; + OptionButton *edit_type_standard = nullptr; + OptionButton *edit_type_particles = nullptr; + OptionButton *edit_type_sky = nullptr; + OptionButton *edit_type_fog = nullptr; + CheckBox *custom_mode_box = nullptr; bool custom_mode_enabled = false; - bool pending_update_preview; - bool shader_error; - Window *preview_window; - VBoxContainer *preview_vbox; - CodeEdit *preview_text; - Ref<CodeHighlighter> syntax_highlighter; - PanelContainer *error_panel; - Label *error_label; + bool pending_update_preview = false; + bool shader_error = false; + Window *preview_window = nullptr; + VBoxContainer *preview_vbox = nullptr; + CodeEdit *preview_text = nullptr; + Ref<CodeHighlighter> syntax_highlighter = nullptr; + PanelContainer *error_panel = nullptr; + Label *error_label = nullptr; + + bool pending_custom_scripts_to_delete = false; + List<Ref<Script>> custom_scripts_to_delete; + + bool _block_update_options_menu = false; + bool _block_rebuild_shader = false; - UndoRedo *undo_redo; Point2 saved_node_pos; - bool saved_node_pos_dirty; + bool saved_node_pos_dirty = false; - ConfirmationDialog *members_dialog; + ConfirmationDialog *members_dialog = nullptr; VisualShaderNode::PortType members_input_port_type = VisualShaderNode::PORT_TYPE_MAX; VisualShaderNode::PortType members_output_port_type = VisualShaderNode::PORT_TYPE_MAX; - PopupMenu *popup_menu; + PopupMenu *popup_menu = nullptr; PopupMenu *constants_submenu = nullptr; - MenuButton *tools; + MenuButton *tools = nullptr; + + ConfirmationDialog *add_varying_dialog = nullptr; + OptionButton *varying_type = nullptr; + LineEdit *varying_name = nullptr; + OptionButton *varying_mode = nullptr; + Label *varying_error_label = nullptr; + + ConfirmationDialog *remove_varying_dialog = nullptr; + Tree *varyings = nullptr; PopupPanel *comment_title_change_popup = nullptr; LineEdit *comment_title_change_edit = nullptr; @@ -224,22 +270,32 @@ class VisualShaderEditor : public VBoxContainer { CLEAR_COPY_BUFFER, SEPARATOR2, // ignore FLOAT_CONSTANTS, - CONVERT_CONSTANTS_TO_UNIFORMS, - CONVERT_UNIFORMS_TO_CONSTANTS, + CONVERT_CONSTANTS_TO_PARAMETERS, + CONVERT_PARAMETERS_TO_CONSTANTS, SEPARATOR3, // ignore SET_COMMENT_TITLE, SET_COMMENT_DESCRIPTION, }; - Tree *members; - AcceptDialog *alert; - LineEdit *node_filter; - RichTextLabel *node_desc; - Label *highend_label; + enum class VaryingMenuOptions { + ADD, + REMOVE, + }; + + Tree *members = nullptr; + AcceptDialog *alert = nullptr; + LineEdit *node_filter = nullptr; + RichTextLabel *node_desc = nullptr; + Label *highend_label = nullptr; void _tools_menu_option(int p_idx); void _show_members_dialog(bool at_mouse_pos, VisualShaderNode::PortType p_input_port_type = VisualShaderNode::PORT_TYPE_MAX, VisualShaderNode::PortType p_output_port_type = VisualShaderNode::PORT_TYPE_MAX); + void _varying_menu_id_pressed(int p_idx); + void _show_add_varying_dialog(); + void _show_remove_varying_dialog(); + + void _update_nodes(); void _update_graph(); struct AddOption { @@ -247,44 +303,25 @@ class VisualShaderEditor : public VBoxContainer { String category; String type; String description; - int sub_func = 0; - String sub_func_str; + Vector<Variant> ops; Ref<Script> script; int mode = 0; int return_type = 0; int func = 0; - float value = 0; bool highend = false; bool is_custom = false; int temp_idx = 0; - AddOption(const String &p_name = String(), const String &p_category = String(), const String &p_sub_category = String(), const String &p_type = String(), const String &p_description = String(), int p_sub_func = -1, int p_return_type = -1, int p_mode = -1, int p_func = -1, float p_value = -1, bool p_highend = false) { - name = p_name; - type = p_type; - category = p_category + "/" + p_sub_category; - description = p_description; - sub_func = p_sub_func; - return_type = p_return_type; - mode = p_mode; - func = p_func; - value = p_value; - highend = p_highend; - is_custom = false; - } - - AddOption(const String &p_name, const String &p_category, const String &p_sub_category, const String &p_type, const String &p_description, const String &p_sub_func, int p_return_type = -1, int p_mode = -1, int p_func = -1, float p_value = -1, bool p_highend = false) { + AddOption(const String &p_name = String(), const String &p_category = String(), const String &p_type = String(), const String &p_description = String(), const Vector<Variant> &p_ops = Vector<Variant>(), int p_return_type = -1, int p_mode = -1, int p_func = -1, bool p_highend = false) { name = p_name; type = p_type; - category = p_category + "/" + p_sub_category; + category = p_category; description = p_description; - sub_func = 0; - sub_func_str = p_sub_func; + ops = p_ops; return_type = p_return_type; mode = p_mode; func = p_func; - value = p_value; highend = p_highend; - is_custom = false; } }; struct _OptionComparator { @@ -303,12 +340,14 @@ class VisualShaderEditor : public VBoxContainer { int curve_xyz_node_option_idx; List<String> keyword_list; - List<VisualShaderNodeUniformRef> uniform_refs; + List<VisualShaderNodeParameterRef> uniform_refs; void _draw_color_over_button(Object *obj, Color p_color); - void _setup_node(VisualShaderNode *p_node, int p_op_idx); - void _add_node(int p_idx, int p_op_idx = -1, String p_resource_path = "", int p_node_idx = -1); + void _setup_node(VisualShaderNode *p_node, const Vector<Variant> &p_ops); + void _add_node(int p_idx, const Vector<Variant> &p_ops, String p_resource_path = "", int p_node_idx = -1); + void _add_varying(const String &p_name, VisualShader::VaryingMode p_mode, VisualShader::VaryingType p_type); + void _remove_varying(const String &p_name); void _update_options_menu(); void _set_mode(int p_which); @@ -316,10 +355,10 @@ class VisualShaderEditor : public VBoxContainer { void _preview_close_requested(); void _preview_size_changed(); void _update_preview(); + void _update_next_previews(int p_node_id); + void _get_next_nodes_recursively(VisualShader::Type p_type, int p_node_id, LocalVector<int> &r_nodes) const; String _get_description(int p_idx); - static VisualShaderEditor *singleton; - struct DragOp { VisualShader::Type type = VisualShader::Type::TYPE_MAX; int node = 0; @@ -330,7 +369,7 @@ class VisualShaderEditor : public VBoxContainer { bool drag_dirty = false; void _node_dragged(const Vector2 &p_from, const Vector2 &p_to, int p_node); void _nodes_dragged(); - bool updating; + bool updating = false; void _connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index); void _disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index); @@ -340,27 +379,27 @@ class VisualShaderEditor : public VBoxContainer { void _delete_nodes(int p_type, const List<int> &p_nodes); void _delete_node_request(int p_type, int p_node); - void _delete_nodes_request(); + void _delete_nodes_request(const TypedArray<StringName> &p_nodes); void _node_changed(int p_id); void _edit_port_default_input(Object *p_button, int p_node, int p_port); - void _port_edited(); + void _port_edited(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing); - int to_node; - int to_slot; - int from_node; - int from_slot; + int to_node = -1; + int to_slot = -1; + int from_node = -1; + int from_slot = -1; - Set<int> selected_constants; - Set<int> selected_uniforms; + HashSet<int> selected_constants; + HashSet<int> selected_parameters; int selected_comment = -1; int selected_float_constant = -1; - void _convert_constants_to_uniforms(bool p_vice_versa); + void _convert_constants_to_parameters(bool p_vice_versa); void _replace_node(VisualShader::Type p_type_id, int p_node_id, const StringName &p_from, const StringName &p_to); void _update_constant(VisualShader::Type p_type_id, int p_node_id, Variant p_var, int p_preview_port); - void _update_uniform(VisualShader::Type p_type_id, int p_node_id, Variant p_var, int p_preview_port); + void _update_parameter(VisualShader::Type p_type_id, int p_node_id, Variant p_var, int p_preview_port); void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position); void _connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position); @@ -376,8 +415,8 @@ class VisualShaderEditor : public VBoxContainer { void _comment_desc_confirm(); void _comment_desc_text_changed(); - void _uniform_line_edit_changed(const String &p_text, int p_node_id); - void _uniform_line_edit_focus_out(Object *line_edit, int p_node_id); + void _parameter_line_edit_changed(const String &p_text, int p_node_id); + void _parameter_line_edit_focus_out(Object *line_edit, int p_node_id); void _port_name_focus_out(Object *line_edit, int p_node_id, int p_port_id, bool p_output); @@ -389,6 +428,7 @@ class VisualShaderEditor : public VBoxContainer { String group_inputs; String group_outputs; String expression; + bool disabled = false; }; void _dup_copy_nodes(int p_type, List<CopyItem> &r_nodes, List<VisualShader::Connection> &r_connections); @@ -396,9 +436,9 @@ class VisualShaderEditor : public VBoxContainer { void _duplicate_nodes(); - Vector2 selection_center; - List<CopyItem> copy_items_buffer; - List<VisualShader::Connection> copy_connections_buffer; + static Vector2 selection_center; + static List<CopyItem> copy_items_buffer; + static List<VisualShader::Connection> copy_connections_buffer; void _clear_copy_buffer(); void _copy_nodes(bool p_cut); @@ -411,7 +451,8 @@ class VisualShaderEditor : public VBoxContainer { void _custom_mode_toggled(bool p_enabled); void _input_select_item(Ref<VisualShaderNodeInput> input, String name); - void _uniform_select_item(Ref<VisualShaderNodeUniformRef> p_uniform, String p_name); + void _parameter_ref_select_item(Ref<VisualShaderNodeParameterRef> p_parameter_ref, String p_name); + void _varying_select_item(Ref<VisualShaderNodeVarying> p_varying, String p_name); void _float_constant_selected(int p_which); @@ -443,6 +484,13 @@ class VisualShaderEditor : public VBoxContainer { void _member_create(); void _member_cancel(); + void _varying_create(); + void _varying_name_changed(const String &p_text); + void _varying_deleted(); + void _varying_selected(); + void _varying_unselected(); + void _update_varying_tree(); + Vector2 menu_point; void _node_menu_id_pressed(int p_idx); @@ -452,49 +500,42 @@ class VisualShaderEditor : public VBoxContainer { bool _is_available(int p_mode); void _update_created_node(GraphNode *node); - void _update_uniforms(bool p_update_refs); - void _update_uniform_refs(Set<String> &p_names); + void _update_parameters(bool p_update_refs); + void _update_parameter_refs(HashSet<String> &p_names); + void _update_varyings(); + + void _update_options_menu_deferred(); + void _rebuild_shader_deferred(); void _visibility_changed(); + void _get_current_mode_limits(int &r_begin_type, int &r_end_type) const; + void _update_custom_script(const Ref<Script> &p_script); + void _script_created(const Ref<Script> &p_script); + void _resource_saved(const Ref<Resource> &p_resource); + void _resource_removed(const Ref<Resource> &p_resource); + void _resources_removed(); + protected: void _notification(int p_what); static void _bind_methods(); public: - void update_custom_nodes(); void add_plugin(const Ref<VisualShaderNodePlugin> &p_plugin); void remove_plugin(const Ref<VisualShaderNodePlugin> &p_plugin); - static VisualShaderEditor *get_singleton() { return singleton; } VisualShaderGraphPlugin *get_graph_plugin() { return graph_plugin.ptr(); } void clear_custom_types(); void add_custom_type(const String &p_name, const Ref<Script> &p_script, const String &p_description, int p_return_icon_type, const String &p_category, bool p_highend); + Dictionary get_custom_node_data(Ref<VisualShaderNodeCustom> &p_custom_node); + virtual Size2 get_minimum_size() const override; void edit(VisualShader *p_visual_shader); VisualShaderEditor(); }; -class VisualShaderEditorPlugin : public EditorPlugin { - GDCLASS(VisualShaderEditorPlugin, EditorPlugin); - - VisualShaderEditor *visual_shader_editor; - EditorNode *editor; - Button *button; - -public: - virtual String get_name() const override { return "VisualShader"; } - bool has_main_screen() const override { return false; } - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - VisualShaderEditorPlugin(EditorNode *p_node); - ~VisualShaderEditorPlugin(); -}; - class VisualShaderNodePluginDefault : public VisualShaderNodePlugin { GDCLASS(VisualShaderNodePluginDefault, VisualShaderNodePlugin); @@ -502,9 +543,9 @@ public: virtual Control *create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node) override; }; -class EditorPropertyShaderMode : public EditorProperty { - GDCLASS(EditorPropertyShaderMode, EditorProperty); - OptionButton *options; +class EditorPropertyVisualShaderMode : public EditorProperty { + GDCLASS(EditorPropertyVisualShaderMode, EditorProperty); + OptionButton *options = nullptr; void _option_selected(int p_which); @@ -515,15 +556,15 @@ public: void setup(const Vector<String> &p_options); virtual void update_property() override; void set_option_button_clip(bool p_enable); - EditorPropertyShaderMode(); + EditorPropertyVisualShaderMode(); }; -class EditorInspectorShaderModePlugin : public EditorInspectorPlugin { - GDCLASS(EditorInspectorShaderModePlugin, EditorInspectorPlugin); +class EditorInspectorVisualShaderModePlugin : public EditorInspectorPlugin { + GDCLASS(EditorInspectorVisualShaderModePlugin, EditorInspectorPlugin); public: virtual bool can_handle(Object *p_object) override; - virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false) override; }; class VisualShaderNodePortPreview : public Control { @@ -532,6 +573,7 @@ class VisualShaderNodePortPreview : public Control { VisualShader::Type type = VisualShader::Type::TYPE_MAX; int node = 0; int port = 0; + bool is_valid = false; void _shader_changed(); //must regen protected: void _notification(int p_what); @@ -539,7 +581,7 @@ protected: public: virtual Size2 get_minimum_size() const override; - void setup(const Ref<VisualShader> &p_shader, VisualShader::Type p_type, int p_node, int p_port); + void setup(const Ref<VisualShader> &p_shader, VisualShader::Type p_type, int p_node, int p_port, bool p_is_valid); }; class VisualShaderConversionPlugin : public EditorResourceConversionPlugin { diff --git a/editor/plugins/voxel_gi_editor_plugin.cpp b/editor/plugins/voxel_gi_editor_plugin.cpp index 1fd47b67c5..1087a50df6 100644 --- a/editor/plugins/voxel_gi_editor_plugin.cpp +++ b/editor/plugins/voxel_gi_editor_plugin.cpp @@ -1,38 +1,43 @@ -/*************************************************************************/ -/* voxel_gi_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* voxel_gi_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 "voxel_gi_editor_plugin.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" + void VoxelGIEditorPlugin::_bake() { if (voxel_gi) { - if (voxel_gi->get_probe_data().is_null()) { + Ref<VoxelGIData> voxel_gi_data = voxel_gi->get_probe_data(); + + if (voxel_gi_data.is_null()) { String path = get_tree()->get_edited_scene_root()->get_scene_file_path(); if (path.is_empty()) { path = "res://" + voxel_gi->get_name() + "_data.res"; @@ -43,7 +48,32 @@ void VoxelGIEditorPlugin::_bake() { probe_file->set_current_path(path); probe_file->popup_file_dialog(); return; + } else { + String path = voxel_gi_data->get_path(); + if (!path.is_resource_file()) { + int srpos = path.find("::"); + if (srpos != -1) { + String base = path.substr(0, srpos); + if (ResourceLoader::get_resource_type(base) == "PackedScene") { + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + EditorNode::get_singleton()->show_warning(TTR("Voxel GI data is not local to the scene.")); + return; + } + } else { + if (FileAccess::exists(base + ".import")) { + EditorNode::get_singleton()->show_warning(TTR("Voxel GI data is part of an imported resource.")); + return; + } + } + } + } else { + if (FileAccess::exists(path + ".import")) { + EditorNode::get_singleton()->show_warning(TTR("Voxel GI data is an imported resource.")); + return; + } + } } + voxel_gi->bake(); } } @@ -62,41 +92,43 @@ bool VoxelGIEditorPlugin::handles(Object *p_object) const { } void VoxelGIEditorPlugin::_notification(int p_what) { - if (p_what == NOTIFICATION_PROCESS) { - if (!voxel_gi) { - return; - } + switch (p_what) { + case NOTIFICATION_PROCESS: { + if (!voxel_gi) { + return; + } - // Set information tooltip on the Bake button. This information is useful - // to optimize performance (video RAM size) and reduce light leaking (individual cell size). + // Set information tooltip on the Bake button. This information is useful + // to optimize performance (video RAM size) and reduce light leaking (individual cell size). - const Vector3i size = voxel_gi->get_estimated_cell_size(); + const Vector3i cell_size = voxel_gi->get_estimated_cell_size(); - const Vector3 extents = voxel_gi->get_extents(); + const Vector3 half_size = voxel_gi->get_size() / 2; - const int data_size = 4; - const double size_mb = size.x * size.y * size.z * data_size / (1024.0 * 1024.0); - // Add a qualitative measurement to help the user assess whether a VoxelGI node is using a lot of VRAM. - String size_quality; - if (size_mb < 16.0) { - size_quality = TTR("Low"); - } else if (size_mb < 64.0) { - size_quality = TTR("Moderate"); - } else { - size_quality = TTR("High"); - } + const int data_size = 4; + const double size_mb = cell_size.x * cell_size.y * cell_size.z * data_size / (1024.0 * 1024.0); + // Add a qualitative measurement to help the user assess whether a VoxelGI node is using a lot of VRAM. + String size_quality; + if (size_mb < 16.0) { + size_quality = TTR("Low"); + } else if (size_mb < 64.0) { + size_quality = TTR("Moderate"); + } else { + size_quality = TTR("High"); + } - String text; - text += vformat(TTR("Subdivisions: %s"), vformat(String::utf8("%d × %d × %d"), size.x, size.y, size.z)) + "\n"; - text += vformat(TTR("Cell size: %s"), vformat(String::utf8("%.3f × %.3f × %.3f"), extents.x / size.x, extents.y / size.y, extents.z / size.z)) + "\n"; - text += vformat(TTR("Video RAM size: %s MB (%s)"), String::num(size_mb, 2), size_quality); + String text; + text += vformat(TTR("Subdivisions: %s"), vformat(String::utf8("%d × %d × %d"), cell_size.x, cell_size.y, cell_size.z)) + "\n"; + text += vformat(TTR("Cell size: %s"), vformat(String::utf8("%.3f × %.3f × %.3f"), half_size.x / cell_size.x, half_size.y / cell_size.y, half_size.z / cell_size.z)) + "\n"; + text += vformat(TTR("Video RAM size: %s MB (%s)"), String::num(size_mb, 2), size_quality); - // Only update the tooltip when needed to avoid constant redrawing. - if (bake->get_tooltip(Point2()) == text) { - return; - } + // Only update the tooltip when needed to avoid constant redrawing. + if (bake->get_tooltip(Point2()) == text) { + return; + } - bake->set_tooltip(text); + bake->set_tooltip_text(text); + } break; } } @@ -115,7 +147,7 @@ EditorProgress *VoxelGIEditorPlugin::tmp_progress = nullptr; void VoxelGIEditorPlugin::bake_func_begin(int p_steps) { ERR_FAIL_COND(tmp_progress != nullptr); - tmp_progress = memnew(EditorProgress("bake_gi", TTR("Bake GI Probe"), p_steps)); + tmp_progress = memnew(EditorProgress("bake_gi", TTR("Bake VoxelGI"), p_steps)); } void VoxelGIEditorPlugin::bake_func_step(int p_step, const String &p_description) { @@ -134,22 +166,21 @@ void VoxelGIEditorPlugin::_voxel_gi_save_path_and_bake(const String &p_path) { if (voxel_gi) { voxel_gi->bake(); ERR_FAIL_COND(voxel_gi->get_probe_data().is_null()); - ResourceSaver::save(p_path, voxel_gi->get_probe_data(), ResourceSaver::FLAG_CHANGE_PATH); + ResourceSaver::save(voxel_gi->get_probe_data(), p_path, ResourceSaver::FLAG_CHANGE_PATH); } } void VoxelGIEditorPlugin::_bind_methods() { } -VoxelGIEditorPlugin::VoxelGIEditorPlugin(EditorNode *p_node) { - editor = p_node; +VoxelGIEditorPlugin::VoxelGIEditorPlugin() { bake_hb = memnew(HBoxContainer); bake_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); bake_hb->hide(); bake = memnew(Button); bake->set_flat(true); - bake->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); - bake->set_text(TTR("Bake GI Probe")); + bake->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); + bake->set_text(TTR("Bake VoxelGI")); bake->connect("pressed", callable_mp(this, &VoxelGIEditorPlugin::_bake)); bake_hb->add_child(bake); diff --git a/editor/plugins/voxel_gi_editor_plugin.h b/editor/plugins/voxel_gi_editor_plugin.h index 4c7865d868..ad68ff5d91 100644 --- a/editor/plugins/voxel_gi_editor_plugin.h +++ b/editor/plugins/voxel_gi_editor_plugin.h @@ -1,51 +1,53 @@ -/*************************************************************************/ -/* voxel_gi_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ +/**************************************************************************/ +/* voxel_gi_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 VOXEL_GIEDITORPLUGIN_H -#define VOXEL_GIEDITORPLUGIN_H +#ifndef VOXEL_GI_EDITOR_PLUGIN_H +#define VOXEL_GI_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/voxel_gi.h" #include "scene/resources/material.h" +class EditorFileDialog; +struct EditorProgress; +class HBoxContainer; + class VoxelGIEditorPlugin : public EditorPlugin { GDCLASS(VoxelGIEditorPlugin, EditorPlugin); - VoxelGI *voxel_gi; + VoxelGI *voxel_gi = nullptr; - HBoxContainer *bake_hb; - Button *bake; - EditorNode *editor; + HBoxContainer *bake_hb = nullptr; + Button *bake = nullptr; - EditorFileDialog *probe_file; + EditorFileDialog *probe_file = nullptr; static EditorProgress *tmp_progress; static void bake_func_begin(int p_steps); @@ -66,8 +68,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - VoxelGIEditorPlugin(EditorNode *p_node); + VoxelGIEditorPlugin(); ~VoxelGIEditorPlugin(); }; -#endif // VOXEL_GIEDITORPLUGIN_H +#endif // VOXEL_GI_EDITOR_PLUGIN_H |