diff options
Diffstat (limited to 'editor/plugins')
149 files changed, 9894 insertions, 5941 deletions
diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp index 22e0a3dabb..275859f528 100644 --- a/editor/plugins/abstract_polygon_2d_editor.cpp +++ b/editor/plugins/abstract_polygon_2d_editor.cpp @@ -35,6 +35,9 @@ #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; @@ -292,9 +295,9 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) _commit_action(); return true; } else { - pre_move_edit = vertices; edited_point = PosVertex(insert.polygon, insert.vertex + 1, xform.affine_inverse().xform(insert.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(); @@ -565,7 +568,7 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); String num = String::num(vertex.vertex); - Size2 num_size = font->get_string_size(num, font_size); + Size2 num_size = font->get_string_size(num, HORIZONTAL_ALIGNMENT_LEFT, -1, 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)); } } @@ -703,10 +706,8 @@ AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_edge_point(c } AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) { - canvas_item_editor = nullptr; undo_redo = EditorNode::get_undo_redo(); - wip_active = false; edited_point = PosVertex(); wip_destructive = p_wip_destructive; @@ -718,26 +719,24 @@ AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) { 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) { diff --git a/editor/plugins/abstract_polygon_2d_editor.h b/editor/plugins/abstract_polygon_2d_editor.h index b0483cbb62..1fbbe67c8d 100644 --- a/editor/plugins/abstract_polygon_2d_editor.h +++ b/editor/plugins/abstract_polygon_2d_editor.h @@ -36,6 +36,7 @@ #include "scene/gui/box_container.h" class CanvasItemEditor; +class EditorUndoRedoManager; class AbstractPolygon2DEditor : public HBoxContainer { GDCLASS(AbstractPolygon2DEditor, HBoxContainer); @@ -80,10 +81,10 @@ 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 = nullptr; Panel *panel = nullptr; @@ -97,9 +98,9 @@ protected: MODE_CONT, }; - int mode; + int mode = MODE_EDIT; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; virtual void _menu_option(int p_option); void _wip_changed(); diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp index 94882b3464..2578099a9f 100644 --- a/editor/plugins/animation_blend_space_1d_editor.cpp +++ b/editor/plugins/animation_blend_space_1d_editor.cpp @@ -34,6 +34,8 @@ #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" StringName AnimationNodeBlendSpace1DEditor::get_blend_position_path() const { @@ -83,7 +85,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven for (const StringName &E : classes) { String name = String(E).replace_first("AnimationNode", ""); - if (name == "Animation") { + if (name == "Animation" || name == "StartState" || name == "EndState") { continue; } @@ -215,7 +217,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; @@ -224,9 +226,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()) { @@ -240,7 +242,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; @@ -297,10 +299,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)); } } @@ -314,6 +316,8 @@ 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()); + label_value->set_text(blend_space->get_value_label()); snap_value->set_value(blend_space->get_snap()); @@ -329,13 +333,15 @@ void AnimationNodeBlendSpace1DEditor::_config_changed(double) { } updating = true; - undo_redo->create_action(TTR("Change BlendSpace1D Limits")); + 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(this, "_update_space"); undo_redo->add_undo_method(this, "_update_space"); undo_redo->commit_action(); @@ -594,7 +600,6 @@ AnimationNodeBlendSpace1DEditor *AnimationNodeBlendSpace1DEditor::singleton = nu AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { singleton = this; - updating = false; HBoxContainer *top_hb = memnew(HBoxContainer); add_child(top_hb); @@ -609,7 +614,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { 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->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch).bind(3)); tool_select = memnew(Button); tool_select->set_flat(true); @@ -617,7 +622,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { 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->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch).bind(0)); tool_create = memnew(Button); tool_create->set_flat(true); @@ -625,7 +630,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { 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->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch).bind(1)); tool_erase_sep = memnew(VSeparator); top_hb->add_child(tool_erase_sep); @@ -651,6 +656,12 @@ 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)); + edit_hb = memnew(HBoxContainer); top_hb->add_child(edit_hb); edit_hb->add_child(memnew(VSeparator)); @@ -666,7 +677,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(); @@ -745,9 +756,5 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { 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 816c2555ca..125a3382fa 100644 --- a/editor/plugins/animation_blend_space_1d_editor.h +++ b/editor/plugins/animation_blend_space_1d_editor.h @@ -33,13 +33,15 @@ #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 EditorUndoRedoManager; + class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeBlendSpace1DEditor, AnimationTreeNodeEditorPlugin); @@ -61,20 +63,22 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { SpinBox *max_value = nullptr; SpinBox *min_value = nullptr; + CheckBox *sync = nullptr; + HBoxContainer *edit_hb = nullptr; SpinBox *edit_value = nullptr; Button *open_editor = nullptr; - int selected_point; + int selected_point = -1; Control *blend_space_draw = nullptr; PanelContainer *error_panel = nullptr; Label *error_label = nullptr; - bool updating; + bool updating = false; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; static AnimationNodeBlendSpace1DEditor *singleton; @@ -90,11 +94,11 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { 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; diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index 4b7df75aec..c0723cef87 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -38,6 +38,8 @@ #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" @@ -107,7 +109,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven for (const StringName &E : classes) { String name = String(E).replace_first("AnimationNode", ""); - if (name == "Animation") { + if (name == "Animation" || name == "StartState" || name == "EndState") { continue; // nope } int idx = menu->get_item_count(); @@ -407,22 +409,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()) { @@ -435,7 +437,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; @@ -449,7 +451,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; @@ -478,7 +480,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { } 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(points[j], points[(j + 1) % 3], linecolor, Math::round(EDSCALE), true); } Color color; @@ -533,9 +535,9 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { } for (int i = 0; i < points.size() - 1; i++) { - blend_space_draw->draw_line(points[i], points[i + 1], linecolor, 2); + blend_space_draw->draw_line(points[i], 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(points[points.size() - 1], blend_space_draw->get_local_mouse_position(), linecolor, Math::round(2 * EDSCALE), true); } ///draw cursor position @@ -564,15 +566,15 @@ 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)); } } @@ -595,6 +597,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); @@ -620,13 +623,15 @@ void AnimationNodeBlendSpace2DEditor::_config_changed(double) { } updating = true; - undo_redo->create_action(TTR("Change BlendSpace2D Limits")); + 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"); @@ -829,7 +834,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { 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->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch).bind(3)); tool_select = memnew(Button); tool_select->set_flat(true); @@ -837,7 +842,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { 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->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch).bind(0)); tool_create = memnew(Button); tool_create->set_flat(true); @@ -845,7 +850,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { 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->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch).bind(1)); tool_triangle = memnew(Button); tool_triangle->set_flat(true); @@ -853,7 +858,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { 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->connect("pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_tool_switch).bind(2)); tool_erase_sep = memnew(VSeparator); top_hb->add_child(tool_erase_sep); @@ -899,6 +904,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); @@ -923,7 +935,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(); diff --git a/editor/plugins/animation_blend_space_2d_editor.h b/editor/plugins/animation_blend_space_2d_editor.h index db54e84254..df2bcf254d 100644 --- a/editor/plugins/animation_blend_space_2d_editor.h +++ b/editor/plugins/animation_blend_space_2d_editor.h @@ -33,13 +33,15 @@ #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 EditorUndoRedoManager; + class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeBlendSpace2DEditor, AnimationTreeNodeEditorPlugin); @@ -55,6 +57,7 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { Button *snap = nullptr; SpinBox *snap_x = nullptr; SpinBox *snap_y = nullptr; + CheckBox *sync = nullptr; OptionButton *interpolation = nullptr; Button *auto_triangles = nullptr; @@ -81,7 +84,7 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { bool updating; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; static AnimationNodeBlendSpace2DEditor *singleton; diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 0b3164aada..e4f5576d66 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -38,6 +38,8 @@ #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/menu_button.h" #include "scene/gui/panel.h" @@ -147,11 +149,11 @@ void AnimationNodeBlendTreeEditor::_update_graph() { 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); + 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(name, agnode), 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++) { @@ -179,7 +181,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)); @@ -187,7 +189,7 @@ 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); } @@ -197,7 +199,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { 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->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_edit_filters).bind(E), CONNECT_DEFERRED); edit_filters->set_h_size_flags(SIZE_SHRINK_CENTER); } @@ -236,7 +238,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { 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")); @@ -264,6 +266,8 @@ void AnimationNodeBlendTreeEditor::_update_graph() { float graph_minimap_opacity = EditorSettings::get_singleton()->get("editors/visual_editors/minimap_opacity"); graph->set_minimap_opacity(graph_minimap_opacity); + float graph_lines_curvature = EditorSettings::get_singleton()->get("editors/visual_editors/lines_curvature"); + graph->set_connection_lines_curvature(graph_lines_curvature); } void AnimationNodeBlendTreeEditor::_file_opened(const String &p_file) { @@ -459,16 +463,22 @@ void AnimationNodeBlendTreeEditor::_delete_request(const String &p_which) { undo_redo->commit_action(); } -void AnimationNodeBlendTreeEditor::_delete_nodes_request() { +void AnimationNodeBlendTreeEditor::_delete_nodes_request(const TypedArray<StringName> &p_nodes) { 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()) { @@ -557,8 +567,8 @@ 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); @@ -595,10 +605,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++) { @@ -692,11 +702,12 @@ 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 += "]"; @@ -903,8 +914,8 @@ 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; @@ -952,8 +963,8 @@ 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)); @@ -962,6 +973,8 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { 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"); graph->set_minimap_opacity(graph_minimap_opacity); + float graph_lines_curvature = EditorSettings::get_singleton()->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); @@ -972,7 +985,7 @@ 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->connect("about_to_popup", callable_mp(this, &AnimationNodeBlendTreeEditor::_update_options_menu).bind(false)); add_options.push_back(AddOption("Animation", "AnimationNodeAnimation")); add_options.push_back(AddOption("OneShot", "AnimationNodeOneShot", 2)); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h index cacf8379f9..af43da6197 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.h +++ b/editor/plugins/animation_blend_tree_editor_plugin.h @@ -33,7 +33,6 @@ #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" @@ -42,6 +41,7 @@ class ProgressBar; class EditorFileDialog; +class EditorUndoRedoManager; class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeBlendTreeEditor, AnimationTreeNodeEditorPlugin); @@ -55,13 +55,13 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { PanelContainer *error_panel = nullptr; Label *error_label = nullptr; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; 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 = ""; @@ -103,7 +103,7 @@ 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); diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp index 581c3c05a5..f9e5aa799a 100644 --- a/editor/plugins/animation_library_editor.cpp +++ b/editor/plugins/animation_library_editor.cpp @@ -32,6 +32,7 @@ #include "editor/editor_file_dialog.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_undo_redo_manager.h" void AnimationLibraryEditor::set_animation_player(Object *p_player) { player = p_player; @@ -55,7 +56,7 @@ void AnimationLibraryEditor::_add_library_validate(const String &p_name) { ERR_FAIL_COND(al.is_null()); if (p_name == "") { error = TTR("Animation name can't be empty."); - } else if (!AnimationLibrary::is_valid_name(p_name)) { + } 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."); @@ -63,7 +64,7 @@ void AnimationLibraryEditor::_add_library_validate(const String &p_name) { } else { if (p_name == "" && bool(player->call("has_animation_library", ""))) { error = TTR("Enter a library name."); - } else if (!AnimationLibrary::is_valid_name(p_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."); @@ -75,12 +76,16 @@ void AnimationLibraryEditor::_add_library_validate(const String &p_name) { add_library_validate->set_text(error); add_library_dialog->get_ok_button()->set_disabled(true); } else { - add_library_validate->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); - if (p_name == "") { - add_library_validate->set_text(TTR("Global library will be created.")); + if (adding_animation) { + add_library_validate->set_text(TTR("Animation name is valid.")); } else { - add_library_validate->set_text(TTR("Library name is valid.")); + 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); } } @@ -88,7 +93,7 @@ void AnimationLibraryEditor::_add_library_validate(const String &p_name) { void AnimationLibraryEditor::_add_library_confirm() { if (adding_animation) { String anim_name = add_library_name->get_text(); - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); Ref<AnimationLibrary> al = player->call("get_animation_library", adding_animation_to_library); ERR_FAIL_COND(!al.is_valid()); @@ -105,7 +110,7 @@ void AnimationLibraryEditor::_add_library_confirm() { } else { String lib_name = add_library_name->get_text(); - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); Ref<AnimationLibrary> al; al.instantiate(); @@ -145,13 +150,35 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) { } switch (p_id) { case FILE_MENU_SAVE_LIBRARY: { - if (al->get_path().is_resource_file()) { + 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()) { @@ -174,7 +201,10 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) { Ref<AnimationLibrary> ald = al->duplicate(); - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + // TODO: should probably make all foreign animations assigned to this library + // unique too. + + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); 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); @@ -184,19 +214,43 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) { 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()) { + 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()) { @@ -219,7 +273,7 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) { Ref<Animation> animd = anim->duplicate(); - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); 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); @@ -228,6 +282,8 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) { 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()); @@ -256,7 +312,7 @@ void AnimationLibraryEditor::_load_file(String p_path) { } } - String name = AnimationLibrary::validate_name(p_path.get_file().get_basename()); + String name = AnimationLibrary::validate_library_name(p_path.get_file().get_basename()); int attempt = 1; @@ -265,7 +321,7 @@ void AnimationLibraryEditor::_load_file(String p_path) { name = p_path.get_file().get_basename() + " " + itos(attempt); } - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), name)); undo_redo->add_do_method(player, "add_animation_library", name, al); @@ -303,7 +359,7 @@ void AnimationLibraryEditor::_load_file(String p_path) { name = p_path.get_file().get_basename() + " " + itos(attempt); } - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); undo_redo->create_action(vformat(TTR("Load Animation into Library: %s"), name)); undo_redo->add_do_method(al.ptr(), "add_animation", name, anim); @@ -319,7 +375,7 @@ void AnimationLibraryEditor::_load_file(String p_path) { EditorNode::get_singleton()->save_resource_in_path(al, p_path); if (al->get_path() != prev_path) { // Save successful. - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); 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()); @@ -340,7 +396,7 @@ void AnimationLibraryEditor::_load_file(String p_path) { String prev_path = anim->get_path(); EditorNode::get_singleton()->save_resource_in_path(anim, p_path); if (anim->get_path() != prev_path) { // Save successful. - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); 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()); @@ -358,7 +414,7 @@ void AnimationLibraryEditor::_item_renamed() { String text = ti->get_text(0); String old_text = ti->get_metadata(0); bool restore_text = false; - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); if (String(text).contains("/") || String(text).contains(":") || String(text).contains(",") || String(text).contains("[")) { restore_text = true; @@ -415,12 +471,12 @@ void AnimationLibraryEditor::_item_renamed() { } } -void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int p_button) { +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_button) { + switch (p_id) { case LIB_BUTTON_ADD: { add_library_dialog->set_title(TTR("Animation Name:")); add_library_name->set_text(""); @@ -469,10 +525,10 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int int attempt = 1; while (al->has_animation(name)) { attempt++; - name = base_name + " " + itos(attempt); + name = base_name + " (" + itos(attempt) + ")"; } - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), name)); undo_redo->add_do_method(al.ptr(), "add_animation", name, anim); @@ -498,7 +554,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int file_dialog_library = lib_name; } break; case LIB_BUTTON_DELETE: { - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); 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); @@ -515,7 +571,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int 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_button) { + switch (p_id) { case ANIM_BUTTON_COPY: { if (anim->get_name() == "") { anim->set_name(anim_name); // Keep the name around @@ -539,7 +595,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int } break; case ANIM_BUTTON_DELETE: { - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); 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); @@ -573,19 +629,45 @@ void AnimationLibraryEditor::update_tree() { } else { libitem->set_suffix(0, ""); } - libitem->set_editable(0, true); - 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, false, TTR("Add Animation to Library")); - libitem->add_button(0, get_theme_icon("Load", "EditorIcons"), LIB_BUTTON_LOAD, false, TTR("Load animation from file and add to library")); - libitem->add_button(0, get_theme_icon("ActionPaste", "EditorIcons"), LIB_BUTTON_PASTE, false, TTR("Paste Animation to Library from clipboard")); + Ref<AnimationLibrary> al = player->call("get_animation_library", K); - if (al->get_path().is_resource_file()) { - libitem->set_text(1, al->get_path().get_file()); - libitem->set_tooltip(1, al->get_path()); - } else { + 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(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")); @@ -596,20 +678,38 @@ void AnimationLibraryEditor::update_tree() { for (const StringName &L : animations) { TreeItem *anitem = tree->create_item(libitem); anitem->set_text(0, L); - anitem->set_editable(0, true); + 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, false, TTR("Copy animation to clipboard")); - Ref<Animation> anim = al->get_animation(L); + anitem->add_button(0, get_theme_icon("ActionCopy", "EditorIcons"), ANIM_BUTTON_COPY, animation_library_is_foreign, TTR("Copy animation to clipboard")); - if (anim->get_path().is_resource_file()) { - anitem->set_text(1, anim->get_path().get_file()); - anitem->set_tooltip(1, anim->get_path()); - } else { + 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(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, false, TTR("Save animation to resource on disk")); - anitem->add_button(1, get_theme_icon("Remove", "EditorIcons"), ANIM_BUTTON_DELETE, false, TTR("Remove animation from Library")); + 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")); } } } @@ -673,7 +773,7 @@ AnimationLibraryEditor::AnimationLibraryEditor() { tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); tree->connect("item_edited", callable_mp(this, &AnimationLibraryEditor::_item_renamed)); - tree->connect("button_pressed", callable_mp(this, &AnimationLibraryEditor::_button_pressed)); + tree->connect("button_clicked", callable_mp(this, &AnimationLibraryEditor::_button_pressed)); file_popup = memnew(PopupMenu); add_child(file_popup); diff --git a/editor/plugins/animation_library_editor.h b/editor/plugins/animation_library_editor.h index 5bd4e8d9e2..6e214860b8 100644 --- a/editor/plugins/animation_library_editor.h +++ b/editor/plugins/animation_library_editor.h @@ -99,7 +99,7 @@ class AnimationLibraryEditor : public AcceptDialog { void _load_file(String p_path); void _item_renamed(); - void _button_pressed(TreeItem *p_item, int p_column, int p_button); + void _button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button); void _file_popup_selected(int p_id); @@ -116,4 +116,4 @@ public: AnimationLibraryEditor(); }; -#endif // ANIMATIONPLAYERLIBRARYEDITOR_H +#endif // ANIMATION_LIBRARY_EDITOR_H diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 67d6c66c89..f374f48fb2 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -55,7 +55,7 @@ void AnimationPlayerEditor::_node_removed(Node *p_node) { 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(); @@ -132,10 +132,10 @@ void AnimationPlayerEditor::_notification(int p_what) { 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_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.instantiate(); - autoplay_reset_icon->create_from_image(autoplay_reset_img); + autoplay_reset_icon->set_image(autoplay_reset_img); } stop->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); @@ -283,7 +283,28 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { Ref<Animation> anim = player->get_animation(current); { - track_editor->set_animation(anim); + bool animation_library_is_foreign = false; + if (!anim->get_path().is_resource_file()) { + int srpos = anim->get_path().find("::"); + if (srpos != -1) { + String base = anim->get_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; + } + } else { + if (FileAccess::exists(base + ".import")) { + animation_library_is_foreign = true; + } + } + } + } else { + if (FileAccess::exists(anim->get_path() + ".import")) { + animation_library_is_foreign = true; + } + } + + track_editor->set_animation(anim, animation_library_is_foreign); Node *root = player->get_node(player->get_root()); if (root) { track_editor->set_root(root); @@ -292,7 +313,7 @@ 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); } @@ -303,17 +324,23 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { } void AnimationPlayerEditor::_animation_new() { - name_dialog_op = TOOL_NEW_ANIM; - name_title->set_text(TTR("New Animation Name:")); - int count = 1; String base = TTR("New Anim"); + 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) + ")"; } - if (player->has_animation(attempt)) { + if (player->has_animation(attempt_prefix + attempt)) { count++; continue; } @@ -321,22 +348,13 @@ void AnimationPlayerEditor::_animation_new() { break; } - List<StringName> libraries; - player->get_animation_library_list(&libraries); - library->clear(); - for (const StringName &K : libraries) { - library->add_item((K == StringName()) ? String(TTR("[Global]")) : String(K)); - library->set_item_metadata(0, String(K)); - } - - if (libraries.size() > 1) { - library->show(); - } else { - library->hide(); - } + _update_name_dialog_library_dropdown(); - name->set_text(base); + 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(); } @@ -348,6 +366,12 @@ void AnimationPlayerEditor::_animation_rename() { 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); name_dialog_op = TOOL_RENAME_ANIM; @@ -375,6 +399,10 @@ void AnimationPlayerEditor::_animation_remove_confirmed() { 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); + } undo_redo->create_action(TTR("Remove Animation")); if (player->get_autoplay() == current) { undo_redo->add_do_method(player, "set_autoplay", ""); @@ -427,7 +455,7 @@ void AnimationPlayerEditor::_animation_name_edited() { player->stop(); String new_name = name->get_text(); - if (!AnimationLibrary::is_valid_name(new_name)) { + if (!AnimationLibrary::is_valid_animation_name(new_name)) { error_dialog->set_text(TTR("Invalid animation name!")); error_dialog->popup_centered(); return; @@ -438,8 +466,14 @@ void AnimationPlayerEditor::_animation_name_edited() { 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; } @@ -452,6 +486,13 @@ void AnimationPlayerEditor::_animation_name_edited() { Ref<AnimationLibrary> al = player->get_animation_library(player->find_animation_library(anim)); ERR_FAIL_COND(al.is_null()); + // Extract library prefix if present. + String new_library_prefix = ""; + if (current.contains("/")) { + new_library_prefix = current.get_slice("/", 0) + "/"; + current = current.get_slice("/", 1); + } + 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); @@ -461,19 +502,25 @@ void AnimationPlayerEditor::_animation_name_edited() { undo_redo->add_undo_method(this, "_animation_player_changed", player); undo_redo->commit_action(); - _select_anim_by_name(new_name); + _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()) { - al = player->get_animation_library(library->get_item_metadata(library->get_selected())); + 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 = ""; } } @@ -484,6 +531,7 @@ void AnimationPlayerEditor::_animation_name_edited() { 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); @@ -499,7 +547,11 @@ void AnimationPlayerEditor::_animation_name_edited() { } undo_redo->commit_action(); - _select_anim_by_name(new_name); + if (library_name != "") { + library_name = library_name + "/"; + } + _select_anim_by_name(library_name + new_name); + } break; case TOOL_DUPLICATE_ANIM: { @@ -509,17 +561,44 @@ void AnimationPlayerEditor::_animation_name_edited() { Ref<Animation> new_anim = _animation_clone(anim); new_anim->set_name(new_name); - Ref<AnimationLibrary> library = player->get_animation_library(player->find_animation_library(anim)); + 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")); - undo_redo->add_do_method(library.ptr(), "add_animation", new_name, new_anim); - undo_redo->add_undo_method(library.ptr(), "remove_animation", new_name); - undo_redo->add_do_method(player, "animation_set_next", new_name, player->animation_get_next(current)); + + 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(); - _select_anim_by_name(new_name); + if (library_name != "") { + library_name = library_name + "/"; + } + _select_anim_by_name(library_name + new_name); } break; } @@ -693,14 +772,36 @@ void AnimationPlayerEditor::_animation_edit() { String current = _get_current(); if (current != String()) { Ref<Animation> anim = player->get_animation(current); - track_editor->set_animation(anim); + + bool animation_library_is_foreign = false; + if (!anim->get_path().is_resource_file()) { + int srpos = anim->get_path().find("::"); + if (srpos != -1) { + String base = anim->get_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; + } + } else { + if (FileAccess::exists(base + ".import")) { + animation_library_is_foreign = true; + } + } + } + } else { + if (FileAccess::exists(anim->get_path() + ".import")) { + animation_library_is_foreign = true; + } + } + + 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); } } @@ -754,13 +855,37 @@ void AnimationPlayerEditor::_update_player() { int active_idx = -1; bool no_anims_found = true; + bool foreign_global_anim_lib = false; for (const StringName &K : libraries) { if (K != StringName()) { animation->add_separator(K); } + // Check if the global library is foreign since we want to disable options for adding/remove/renaming animations if it is. Ref<AnimationLibrary> library = player->get_animation_library(K); + if (K == "") { + if (!library->get_path().is_resource_file()) { + int srpos = library->get_path().find("::"); + if (srpos != -1) { + String base = library->get_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) { + foreign_global_anim_lib = true; + } + } else { + if (FileAccess::exists(base + ".import")) { + foreign_global_anim_lib = true; + } + } + } + } else { + if (FileAccess::exists(library->get_path() + ".import")) { + foreign_global_anim_lib = true; + } + } + } + List<StringName> animlist; library->get_animation_list(&animlist); @@ -777,7 +902,13 @@ void AnimationPlayerEditor::_update_player() { 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), no_anims_found) +#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); @@ -819,7 +950,29 @@ void AnimationPlayerEditor::_update_player() { 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 = false; + if (!anim->get_path().is_resource_file()) { + int srpos = anim->get_path().find("::"); + if (srpos != -1) { + String base = anim->get_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; + } + } else { + if (FileAccess::exists(base + ".import")) { + animation_library_is_foreign = true; + } + } + } + } else { + if (FileAccess::exists(anim->get_path() + ".import")) { + animation_library_is_foreign = true; + } + } + + track_editor->set_animation(anim, animation_library_is_foreign); Node *root = player->get_node(player->get_root()); if (root) { track_editor->set_root(root); @@ -851,6 +1004,51 @@ void AnimationPlayerEditor::_update_animation_list_icons() { } } +void AnimationPlayerEditor::_update_name_dialog_library_dropdown() { + StringName current_library_name = StringName(); + 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::set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo) { + undo_redo = p_undo_redo; +} + void AnimationPlayerEditor::edit(AnimationPlayer *p_player) { if (player && pin->is_pressed()) { return; // Ignore, pinned. @@ -946,9 +1144,17 @@ void AnimationPlayerEditor::_animation_duplicate() { new_name = new_name + " (copy)"; } - name_title->set_text(TTR("New Animation Name:")); - name->set_text(new_name); + if (new_name.contains("/")) { + // Discard library prefix. + new_name = new_name.get_slice("/", 1); + } + + _update_name_dialog_library_dropdown(); + 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(); @@ -1319,26 +1525,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_uniform("bkg_color", GLOBAL_GET("rendering/environment/defaults/default_clear_color")); + onion.capture.material->set_shader_uniform("differences_only", onion.differences_only); + onion.capture.material->set_shader_uniform("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_uniform("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_uniform("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); @@ -1466,7 +1672,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug 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).")); @@ -1526,14 +1732,16 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug 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->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->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(-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); @@ -1579,7 +1787,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug 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); @@ -1587,7 +1795,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug 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); @@ -1611,7 +1819,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug animation->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_animation_selected)); - 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)); last_active = false; @@ -1721,7 +1929,7 @@ void AnimationPlayerEditorPlugin::_update_keying() { } void AnimationPlayerEditorPlugin::edit(Object *p_object) { - anim_editor->set_undo_redo(&get_undo_redo()); + anim_editor->set_undo_redo(get_undo_redo()); if (!p_object) { return; } diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 0cc04460ca..a37a9debef 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -41,6 +41,7 @@ #include "scene/gui/texture_button.h" #include "scene/gui/tree.h" +class EditorUndoRedoManager; class AnimationPlayerEditorPlugin; class AnimationPlayerEditor : public VBoxContainer { @@ -100,7 +101,7 @@ class AnimationPlayerEditor : public VBoxContainer { LineEdit *name = nullptr; OptionButton *library = nullptr; Label *name_title = nullptr; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; Ref<Texture2D> autoplay_icon; Ref<Texture2D> reset_icon; @@ -188,6 +189,7 @@ class AnimationPlayerEditor : public VBoxContainer { 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); @@ -232,7 +234,7 @@ public: void ensure_visibility(); - void set_undo_redo(UndoRedo *p_undo_redo) { undo_redo = p_undo_redo; } + void set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo); void edit(AnimationPlayer *p_player); void forward_force_draw_over_viewport(Control *p_overlay); diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index 8397772bf8..ef4ae3dca4 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -38,10 +38,14 @@ #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/panel.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) { @@ -55,7 +59,10 @@ void AnimationNodeStateMachineEditor::edit(const Ref<AnimationNode> &p_node) { if (state_machine.is_valid()) { 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(); } @@ -69,71 +76,39 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv 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()) { + if (selected_node != StringName() || !selected_nodes.is_empty() || selected_transition_to != StringName() || selected_transition_from != StringName()) { _erase_selected(); accept_event(); } } - Ref<InputEventMouseButton> mb = p_event; - - //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); - } - } - } + // 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(); + } - 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(TTR("Add %s"), name), idx); - menu->set_item_metadata(idx, E); - } - Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard(); + // 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(); + } - 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); + Ref<InputEventMouseButton> mb = p_event; - 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(); + // Add new node + 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()); } - // 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 @@ -146,8 +121,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv return; } - if (node_rects[i].name.has_point(mb->get_position())) { //edit name - + 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; @@ -173,6 +147,12 @@ 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(); @@ -209,23 +189,53 @@ 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(); _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; + 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(); @@ -239,7 +249,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv state_machine_draw->update(); } - //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 @@ -252,47 +262,63 @@ 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(); } + // 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->update(); + _update_mode(); + } + Ref<InputEventMouseMotion> mm = p_event; - //pan window + // Pan window if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE) { 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 + // Move mouse while connecting if (mm.is_valid() && connecting) { connecting_to = mm->get_position(); connecting_to_node = StringName(); @@ -306,7 +332,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } } - //move mouse while moving a node + // Move mouse while moving a node if (mm.is_valid() && dragging_selected_attempt) { dragging_selected = true; drag_ofs = mm->get_position() - drag_from; @@ -346,6 +372,36 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv state_machine_draw->update(); } + // 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->update(); + } + if (mm.is_valid()) { state_machine_draw->grab_focus(); @@ -353,6 +409,11 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv 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. + + 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())) { @@ -370,6 +431,43 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv over_node_what = new_over_node_what; state_machine_draw->update(); } + + // 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; + } + } + + 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(tooltip); + } else { + state_machine_draw->set_tooltip(""); + } + } } Ref<InputEventPanGesture> pan_gesture = p_event; @@ -396,6 +494,458 @@ Control::CursorShape AnimationNodeStateMachineEditor::get_cursor_shape(const Poi 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; + 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->update(); + 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; + 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->update(); + accept_event(); + _update_mode(); + } +} + +void AnimationNodeStateMachineEditor::_open_menu(const Vector2 &p_position) { + 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 (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 { + Ref<AnimationNodeStateMachine> prev = state_machine; + _create_submenu(connect_menu, prev, 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) { + 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(); + } + 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 found = false; + + for (int i = 0; i < parents.size(); i++) { + if (parents[i] == ansm) { + path = path.replace_first("/../" + E, ""); + found = true; + break; + } + } + + if (!found) { + state_machine_menu->add_item(E, nodes_to_connect.size()); + nodes_to_connect.push_back(path); + } else { + end_menu->add_item(E, nodes_to_connect.size()); + nodes_to_connect.push_back(path + "/" + state_machine->end_node); + } + + 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->update(); +} + +void AnimationNodeStateMachineEditor::_delete_selected() { + TreeItem *item = delete_tree->get_next_selected(nullptr); + 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; + 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()) { @@ -456,6 +1006,8 @@ void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) { 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; @@ -468,7 +1020,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)) { @@ -482,13 +1034,58 @@ void AnimationNodeStateMachineEditor::_add_animation_type(int p_index) { 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(); } -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::_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_switch_mode(AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected())); + + 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"); + undo_redo->commit_action(); + + if (!p_nested_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, 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")); @@ -516,17 +1113,24 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co 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)]; 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()); } @@ -557,20 +1161,27 @@ void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(const Vector2 &p_fr void AnimationNodeStateMachineEditor::_state_machine_draw() { Ref<AnimationNodeStateMachinePlayback> playback = AnimationTreeEditor::get_singleton()->get_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_selected_frame"), 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; @@ -611,23 +1222,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(); @@ -666,7 +1280,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(transition_mode->get_selected()), true, false, false, false, false); } Ref<Texture2D> tr_reference_icon = get_theme_icon(SNAME("TransitionImmediateBig"), SNAME("EditorIcons")); @@ -675,13 +1289,18 @@ 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(); @@ -690,62 +1309,79 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { tl.advance_condition_state = false; tl.mode = tr->get_switch_mode(); tl.width = tr_bidi_offset; + tl.travel = false; + 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; - - bool travel = false; + tl.selected = selected_transition_from == tl.from_node && selected_transition_to == tl.to_node; - if (blend_from == tl.from_node && current == tl.to_node) { - travel = true; + if (blend_from == local_from && current == local_to) { + tl.travel = true; } 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))) { 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.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; @@ -756,18 +1392,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.has(name))) { - 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; @@ -775,13 +1409,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(); @@ -799,10 +1434,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; @@ -829,6 +1468,10 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw() { return; } + if (playback->get_current_node() == state_machine->start_node || playback->get_current_node() == state_machine->end_node) { + return; + } + int idx = -1; for (int i = 0; i < node_rects.size(); i++) { if (node_rects[i].node_name == playback->get_current_node()) { @@ -902,8 +1545,8 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) { 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"))); + 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")); @@ -1109,94 +1752,121 @@ void AnimationNodeStateMachineEditor::_scroll_changed(double) { state_machine_draw->update(); } -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; + } 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; + } 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(); } -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(); - } 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(); - } -} - -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; - } - - 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(); - } -} - 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()); + 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); + + if (selected_nodes.is_empty() || start_end_selected) { + tool_group->set_disabled(true); + tool_group->set_visible(true); + tool_ungroup->set_visible(false); + } else { + Ref<AnimationNodeStateMachine> ansm = state_machine->get_node(*selected_nodes.begin()); + + 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); + } + } } else { tool_erase_hb->hide(); } @@ -1204,17 +1874,19 @@ void AnimationNodeStateMachineEditor::_update_mode() { 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); @@ -1228,8 +1900,8 @@ 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(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); @@ -1237,7 +1909,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { 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->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_update_mode), CONNECT_DEFERRED); tool_connect = memnew(Button); tool_connect->set_flat(true); @@ -1245,36 +1917,35 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { 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->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_update_mode), CONNECT_DEFERRED); tool_erase_hb = memnew(HBoxContainer); top_hb->add_child(tool_erase_hb); tool_erase_hb->add_child(memnew(VSeparator)); + + tool_group = memnew(Button); + tool_group->set_flat(true); + tool_group->set_tooltip(TTR("Group Selected Node(s)") + " (Ctrl+G)"); + tool_group->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_group_selected_nodes)); + tool_group->set_disabled(true); + tool_erase_hb->add_child(tool_group); + + tool_ungroup = memnew(Button); + tool_ungroup->set_flat(true); + tool_ungroup->set_tooltip(TTR("Ungroup Selected Node") + " (Ctrl+Shift+G)"); + tool_ungroup->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_ungroup_selected_nodes)); + tool_ungroup->set_visible(false); + tool_erase_hb->add_child(tool_ungroup); + 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->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_erase_selected).bind(false)); tool_erase->set_disabled(true); - - tool_erase_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); - - 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); + tool_erase_hb->add_child(tool_erase); top_hb->add_child(memnew(VSeparator)); - top_hb->add_child(memnew(Label(TTR("Transition: ")))); + top_hb->add_child(memnew(Label(TTR("Transition:")))); transition_mode = memnew(OptionButton); top_hb->add_child(transition_mode); @@ -1300,7 +1971,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { 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->set_anchors_and_offsets_preset(PRESET_FULL_RECT); state_machine_play_pos->connect("draw", callable_mp(this, &AnimationNodeStateMachineEditor::_state_machine_pos_draw)); v_scroll = memnew(VScrollBar); @@ -1327,17 +1998,33 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { 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)); @@ -1348,11 +2035,94 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { open_file->connect("file_selected", callable_mp(this, &AnimationNodeStateMachineEditor::_file_opened)); undo_redo = EditorNode::get_undo_redo(); + 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 fe3f6f370c..fdd1af0f6d 100644 --- a/editor/plugins/animation_state_machine_editor.h +++ b/editor/plugins/animation_state_machine_editor.h @@ -33,7 +33,6 @@ #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" @@ -41,6 +40,7 @@ #include "scene/gui/tree.h" class EditorFileDialog; +class EditorUndoRedoManager; class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeStateMachineEditor, AnimationTreeNodeEditorPlugin); @@ -50,13 +50,13 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { Button *tool_select = nullptr; Button *tool_create = nullptr; Button *tool_connect = nullptr; + Button *tool_group = nullptr; + Button *tool_ungroup = nullptr; Popup *name_edit_popup = nullptr; LineEdit *name_edit = nullptr; HBoxContainer *tool_erase_hb = nullptr; Button *tool_erase = nullptr; - Button *tool_autoplay = nullptr; - Button *tool_end = nullptr; OptionButton *transition_mode = nullptr; OptionButton *play_mode = nullptr; @@ -64,6 +64,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { PanelContainer *panel = nullptr; StringName selected_node; + HashSet<StringName> selected_nodes; HScrollBar *h_scroll = nullptr; VScrollBar *v_scroll = nullptr; @@ -74,39 +75,53 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { PanelContainer *error_panel = nullptr; Label *error_label = nullptr; - bool updating; + bool updating = false; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; 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, bool p_auto_advance, bool p_multi_transitions); void _state_machine_draw(); void _state_machine_pos_draw(); void _update_graph(); 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; + Tree *delete_tree; + + 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; 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 _connect_to(int p_index); void _removed_from_graph(); @@ -131,15 +146,37 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { bool disabled = false; bool auto_advance = false; float width = 0; + bool selected; + bool travel; + 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); StringName over_node; - int over_node_what; + int over_node_what = -1; String prev_name; void _name_edited(const String &p_text); @@ -150,20 +187,29 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { 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; + void _delete_selected(); + void _delete_all(); + void _delete_tree_draw(); + + bool last_active = false; StringName last_blend_from_node; StringName last_current_node; Vector<StringName> last_travel_path; - float last_play_pos; - float play_pos; - float current_length; + float last_play_pos = 0.0f; + float play_pos = 0.0f; + float current_length = 0.0f; - float error_time; + float error_time = 0.0f; String error_text; EditorFileDialog *open_file = nullptr; @@ -188,4 +234,26 @@ public: 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 7ea6906d72..bce4c9de8e 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -86,7 +86,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); @@ -96,7 +96,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)); } } diff --git a/editor/plugins/animation_tree_editor_plugin.h b/editor/plugins/animation_tree_editor_plugin.h index ab4ef5a001..a33d97f62f 100644 --- a/editor/plugins/animation_tree_editor_plugin.h +++ b/editor/plugins/animation_tree_editor_plugin.h @@ -32,7 +32,6 @@ #define ANIMATION_TREE_EDITOR_PLUGIN_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" diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index e24d710831..8ee162d085 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -32,6 +32,7 @@ #include "core/input/input.h" #include "core/io/json.h" +#include "core/io/stream_peer_ssl.h" #include "core/os/keyboard.h" #include "core/version.h" #include "editor/editor_file_dialog.h" @@ -160,18 +161,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 { @@ -257,7 +253,7 @@ void EditorAssetLibraryItemDescription::add_preview(int p_id, bool p_video, cons 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.button->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDescription::_preview_click).bind(p_id)); preview_hb->add_child(preview.button); if (!p_video) { preview.image = previews->get_theme_icon(SNAME("ThumbnailWait"), SNAME("EditorIcons")); @@ -290,12 +286,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_ignore_texture_size(true); 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); @@ -308,8 +307,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")); } /////////////////////////////////////////////////////////////////////////////////// @@ -620,6 +619,10 @@ void EditorAssetLibrary::_notification(int p_what) { } break; + case NOTIFICATION_RESIZED: { + _update_asset_items_columns(); + } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { _update_repository_options(); setup_http_request(request); @@ -782,9 +785,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; @@ -886,7 +887,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; @@ -897,6 +898,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"); @@ -965,6 +979,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); @@ -988,7 +1006,7 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int Button *first = memnew(Button); 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); @@ -998,7 +1016,7 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int Button *prev = memnew(Button); 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); @@ -1019,7 +1037,7 @@ 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); } @@ -1028,7 +1046,7 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int Button *next = memnew(Button); 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); @@ -1039,7 +1057,7 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int Button *last = memnew(Button); 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); @@ -1106,6 +1124,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; } @@ -1140,17 +1162,16 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const } } + 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); } @@ -1189,7 +1210,7 @@ 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); + _update_asset_items_columns(); asset_items->add_theme_constant_override("h_separation", 10 * EDSCALE); asset_items->add_theme_constant_override("v_separation", 10 * EDSCALE); @@ -1200,16 +1221,18 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const if (result.is_empty()) { if (!filter->get_text().is_empty()) { - library_error->set_text( + library_info->set_text( vformat(TTR("No results for \"%s\"."), filter->get_text())); } 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( + library_info->set_text( vformat(TTR("No results compatible with %s %s."), String(VERSION_SHORT_NAME).capitalize(), String(VERSION_BRANCH))); } - library_error->show(); + library_info->show(); + } else { + library_info->hide(); } for (int i = 0; i < result.size(); i++) { @@ -1270,14 +1293,14 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const EditorAssetLibraryItemDownload *download_item = _get_asset_in_progress(description->get_asset_id()); if (download_item) { if (download_item->can_install()) { - description->get_ok_button()->set_text(TTR("Install")); + description->set_ok_button_text(TTR("Install")); description->get_ok_button()->set_disabled(false); } else { - description->get_ok_button()->set_text(TTR("Downloading...")); + description->set_ok_button_text(TTR("Downloading...")); description->get_ok_button()->set_disabled(true); } } else { - description->get_ok_button()->set_text(TTR("Download")); + description->set_ok_button_text(TTR("Download")); description->get_ok_button()->set_disabled(false); } @@ -1353,6 +1376,15 @@ void EditorAssetLibrary::_install_external_asset(String p_zip_path, String p_tit 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); } @@ -1376,9 +1408,9 @@ 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); @@ -1424,6 +1456,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)); @@ -1433,6 +1466,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)); @@ -1446,6 +1480,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)); @@ -1488,20 +1523,28 @@ 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); + _update_asset_items_columns(); asset_items->add_theme_constant_override("h_separation", 10 * EDSCALE); asset_items->add_theme_constant_override("v_separation", 10 * EDSCALE); @@ -1541,7 +1584,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)); @@ -1551,6 +1594,16 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { /////// +bool AssetLibraryEditorPlugin::is_available() { +#ifdef JAVASCRIPT_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 StreamPeerSSL::is_available(); +#endif +} + void AssetLibraryEditorPlugin::make_visible(bool p_visible) { if (p_visible) { addon_library->show(); @@ -1563,7 +1616,7 @@ AssetLibraryEditorPlugin::AssetLibraryEditorPlugin() { addon_library = memnew(EditorAssetLibrary); addon_library->set_v_size_flags(Control::SIZE_EXPAND_FILL); EditorNode::get_singleton()->get_main_control()->add_child(addon_library); - addon_library->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + 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 96830c31fd..070d25e29f 100644 --- a/editor/plugins/asset_library_editor_plugin.h +++ b/editor/plugins/asset_library_editor_plugin.h @@ -60,9 +60,9 @@ class EditorAssetLibraryItem : public PanelContainer { TextureRect *stars[5]; 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(); @@ -102,7 +102,7 @@ class EditorAssetLibraryItemDescription : public ConfirmationDialog { 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; @@ -146,7 +146,7 @@ class EditorAssetLibraryItemDownload : public MarginContainer { int prev_status; - int asset_id; + int asset_id = 0; bool external_install; @@ -186,8 +186,10 @@ class EditorAssetLibrary : public PanelContainer { PanelContainer *library_scroll_bg = nullptr; ScrollContainer *library_scroll = nullptr; VBoxContainer *library_vb = nullptr; - Label *library_loading = nullptr; - Label *library_error = 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; @@ -250,7 +252,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); @@ -291,6 +293,7 @@ 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); @@ -298,6 +301,8 @@ class EditorAssetLibrary : public PanelContainer { void _install_external_asset(String p_zip_path, String p_title); + void _update_asset_items_columns(); + friend class EditorAssetLibraryItemDescription; friend class EditorAssetLibraryItem; @@ -318,6 +323,8 @@ class AssetLibraryEditorPlugin : public EditorPlugin { 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 {} @@ -331,4 +338,4 @@ public: ~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 deleted file mode 100644 index a60e49ca9d..0000000000 --- a/editor/plugins/audio_stream_editor_plugin.cpp +++ /dev/null @@ -1,285 +0,0 @@ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -#include "audio_stream_editor_plugin.h" - -#include "core/config/project_settings.h" -#include "core/io/resource_loader.h" -#include "core/os/keyboard.h" -#include "editor/audio_stream_preview.h" -#include "editor/editor_node.h" -#include "editor/editor_scale.h" -#include "editor/editor_settings.h" - -void AudioStreamEditor::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_READY: { - AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", callable_mp(this, &AudioStreamEditor::_preview_changed)); - } break; - - case NOTIFICATION_THEME_CHANGED: - case 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(); - } break; - - case NOTIFICATION_PROCESS: { - _current = _player->get_playback_position(); - _indicator->update(); - } break; - - case NOTIFICATION_VISIBILITY_CHANGED: { - if (!is_visible_in_tree()) { - _stop(); - } - } break; - } -} - -void AudioStreamEditor::_draw_preview() { - Rect2 rect = _preview->get_rect(); - Size2 size = get_size(); - - Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream); - float preview_len = preview->get_length(); - - Vector<Vector2> lines; - lines.resize(size.width * 2); - - for (int i = 0; i < size.width; i++) { - float ofs = i * preview_len / size.width; - float ofs_n = (i + 1) * preview_len / size.width; - float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5; - float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5; - - int idx = i; - lines.write[idx * 2 + 0] = Vector2(i + 1, rect.position.y + min * rect.size.y); - 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); -} - -void AudioStreamEditor::_preview_changed(ObjectID p_which) { - if (stream.is_valid() && stream->get_instance_id() == p_which) { - _preview->update(); - } -} - -void AudioStreamEditor::_audio_changed() { - if (!is_visible()) { - return; - } - update(); -} - -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 { - _player->play(_current); - _play_button->set_icon(get_theme_icon(SNAME("Pause"), SNAME("EditorIcons"))); - set_process(true); - } -} - -void AudioStreamEditor::_stop() { - _player->stop(); - _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); - _current = 0; - _indicator->update(); - set_process(false); -} - -void AudioStreamEditor::_on_finished() { - _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); - if (!_pausing) { - _current = 0; - _indicator->update(); - } else { - _pausing = false; - } - set_process(false); -} - -void AudioStreamEditor::_draw_indicator() { - if (!stream.is_valid()) { - 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)); - _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); - - _current_label->set_text(String::num(_current, 2).pad_decimals(2) + " /"); -} - -void AudioStreamEditor::_on_input_indicator(Ref<InputEvent> p_event) { - const Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { - if (mb->is_pressed()) { - _seek_to(mb->get_position().x); - } - _dragging = mb->is_pressed(); - } - - const Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid()) { - if (_dragging) { - _seek_to(mm->get_position().x); - } - } -} - -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(); -} - -void AudioStreamEditor::edit(Ref<AudioStream> p_stream) { - if (!stream.is_null()) { - stream->disconnect("changed", callable_mp(this, &AudioStreamEditor::_audio_changed)); - } - - stream = p_stream; - _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() { -} - -AudioStreamEditor::AudioStreamEditor() { - set_custom_minimum_size(Size2(1, 100) * EDSCALE); - - _player = memnew(AudioStreamPlayer); - _player->connect("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); - add_child(vbox); - - _preview = memnew(ColorRect); - _preview->set_v_size_flags(SIZE_EXPAND_FILL); - _preview->connect("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)); - _preview->add_child(_indicator); - - HBoxContainer *hbox = memnew(HBoxContainer); - hbox->add_theme_constant_override("separation", 0); - vbox->add_child(hbox); - - _play_button = memnew(Button); - _play_button->set_flat(true); - hbox->add_child(_play_button); - _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)); - - _stop_button = memnew(Button); - _stop_button->set_flat(true); - hbox->add_child(_stop_button); - _stop_button->set_focus_mode(Control::FOCUS_NONE); - _stop_button->connect("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_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; - } - - audio_editor->edit(Ref<AudioStream>(s)); -} - -bool AudioStreamEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("AudioStream"); -} - -void AudioStreamEditorPlugin::make_visible(bool p_visible) { - audio_editor->set_visible(p_visible); -} - -AudioStreamEditorPlugin::AudioStreamEditorPlugin() { - audio_editor = memnew(AudioStreamEditor); - add_control_to_container(CONTAINER_PROPERTY_EDITOR_BOTTOM, audio_editor); - audio_editor->hide(); -} - -AudioStreamEditorPlugin::~AudioStreamEditorPlugin() { -} diff --git a/editor/plugins/audio_stream_randomizer_editor_plugin.cpp b/editor/plugins/audio_stream_randomizer_editor_plugin.cpp index 9e551ae0ed..d670197c53 100644 --- a/editor/plugins/audio_stream_randomizer_editor_plugin.cpp +++ b/editor/plugins/audio_stream_randomizer_editor_plugin.cpp @@ -31,6 +31,7 @@ #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) { } @@ -43,8 +44,8 @@ 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) { - UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); - ERR_FAIL_COND(!undo_redo); + Ref<EditorUndoRedoManager> undo_redo = Object::cast_to<EditorUndoRedoManager>(p_undo_redo); + ERR_FAIL_COND(undo_redo.is_null()); AudioStreamRandomizer *randomizer = Object::cast_to<AudioStreamRandomizer>(p_edited); if (!randomizer) { diff --git a/editor/plugins/bit_map_editor_plugin.cpp b/editor/plugins/bit_map_editor_plugin.cpp index 9003c4480b..657c5a36b6 100644 --- a/editor/plugins/bit_map_editor_plugin.cpp +++ b/editor/plugins/bit_map_editor_plugin.cpp @@ -33,11 +33,7 @@ #include "editor/editor_scale.h" void BitMapEditor::setup(const Ref<BitMap> &p_bitmap) { - Ref<ImageTexture> texture; - texture.instantiate(); - texture->create_from_image(p_bitmap->convert_to_image()); - texture_rect->set_texture(texture); - + 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)); } diff --git a/editor/plugins/bit_map_editor_plugin.h b/editor/plugins/bit_map_editor_plugin.h index c883e5542f..b045f8c751 100644 --- a/editor/plugins/bit_map_editor_plugin.h +++ b/editor/plugins/bit_map_editor_plugin.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef BIT_MAP_PREVIEW_EDITOR_PLUGIN_H -#define BIT_MAP_PREVIEW_EDITOR_PLUGIN_H +#ifndef BIT_MAP_EDITOR_PLUGIN_H +#define BIT_MAP_EDITOR_PLUGIN_H #include "editor/editor_plugin.h" #include "scene/resources/bit_map.h" @@ -61,4 +61,4 @@ public: BitMapEditorPlugin(); }; -#endif // BIT_MAP_PREVIEW_EDITOR_PLUGIN_H +#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..5db9249af1 --- /dev/null +++ b/editor/plugins/bone_map_editor_plugin.cpp @@ -0,0 +1,1388 @@ +/*************************************************************************/ +/* bone_map_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 "bone_map_editor_plugin.h" + +#include "editor/editor_scale.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" + +void BoneMapperButton::fetch_textures() { + if (selected) { + set_normal_texture(get_theme_icon(SNAME("BoneMapperHandleSelected"), SNAME("EditorIcons"))); + } else { + set_normal_texture(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(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/unset")); + } break; + case BONE_MAP_STATE_SET: { + circle->set_modulate(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/set")); + } break; + case BONE_MAP_STATE_MISSING: { + circle->set_modulate(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/missing")); + } break; + case BONE_MAP_STATE_ERROR: { + circle->set_modulate(EditorSettings::get_singleton()->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(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_ignore_texture_size(true); + 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 (uint32_t i = 0; i < hit_list.size(); i++) { + if (hit_list[i].length() < shortest.length()) { + shortest = hit_list[i]; // 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 (uint32_t i = 0; i < hit_list.size(); i++) { + if (hit_list[i].length() <= shortest.length()) { + shortest = hit_list[i]; // 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.camelcase_to_underscore().to_lower(); + + 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 (Vector3(0, 0, 0).is_equal_approx(skeleton->get_bone_global_rest(search_path[i]).origin)) { + 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 = -1; + + 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)); + } + bone_idx = -1; + 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 easist 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..0541ce6eac --- /dev/null +++ b/editor/plugins/bone_map_editor_plugin.h @@ -0,0 +1,237 @@ +/*************************************************************************/ +/* bone_map_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 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/color_rect.h" +#include "scene/gui/dialogs.h" +#include "scene/resources/bone_map.h" +#include "scene/resources/texture.h" + +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; + + 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; + Button *picker_button; + + 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; + Ref<BoneMap> bone_map; + + EditorPropertyResource *profile_selector; + + Vector<BoneMapperItem *> bone_mapper_items; + + Button *clear_mapping_button; + + VBoxContainer *mapper_item_vbox; + + int current_group_idx = 0; + int current_bone_idx = -1; + + AspectRatioContainer *bone_mapper_field; + EditorPropertyEnum *profile_group_selector; + ColorRect *profile_bg; + TextureRect *profile_texture; + 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; + Ref<BoneMap> bone_map; + BoneMapper *bone_mapper; + + void fetch_objects(); + void clear_editors(); + 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; + +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.h b/editor/plugins/camera_3d_editor_plugin.h index a8164f9b85..a969b31976 100644 --- a/editor/plugins/camera_3d_editor_plugin.h +++ b/editor/plugins/camera_3d_editor_plugin.h @@ -28,8 +28,8 @@ /* 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_plugin.h" #include "scene/3d/camera_3d.h" @@ -68,4 +68,4 @@ public: ~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 c840ce22ce..2e83e2041f 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -40,6 +40,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_toaster.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/scene_tree_dock.h" @@ -50,8 +51,10 @@ #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/subviewport_container.h" #include "scene/gui/view_panner.h" #include "scene/main/canvas_layer.h" @@ -236,7 +239,7 @@ public: }; bool CanvasItemEditor::_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); } bool CanvasItemEditor::_is_node_movable(const Node *p_node, bool p_popup_warning) { @@ -420,16 +423,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); } @@ -564,7 +565,11 @@ void CanvasItemEditor::_expand_encompassing_rect_using_children(Rect2 &r_rect, c } 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(); + Transform2D xform = p_canvas_xform; + if (!canvas_item->is_set_as_top_level()) { + xform *= p_parent_xform; + } + xform *= canvas_item->get_transform(); Rect2 rect = canvas_item->_edit_get_rect(); if (r_first) { r_rect = Rect2(xform.xform(rect.get_center()), Size2()); @@ -610,7 +615,11 @@ 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(); + Transform2D xform = p_canvas_xform; + if (!canvas_item->is_set_as_top_level()) { + xform *= p_parent_xform; + } + xform = (xform * canvas_item->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); @@ -681,7 +690,7 @@ void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_n 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) { @@ -700,7 +709,11 @@ void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_n } if (canvas_item && canvas_item->is_visible_in_tree() && !locked && editable) { - Transform2D xform = p_parent_xform * p_canvas_xform * canvas_item->get_transform(); + Transform2D xform = p_canvas_xform; + if (!canvas_item->is_set_as_top_level()) { + xform *= p_parent_xform; + } + xform *= canvas_item->get_transform(); if (canvas_item->_edit_use_rect()) { Rect2 rect = canvas_item->_edit_get_rect(); @@ -803,9 +816,17 @@ Vector2 CanvasItemEditor::_position_to_anchor(const Control *p_control, Vector2 } void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones) { + original_transform = Transform2D(); + bool transform_stored = false; + for (CanvasItem *canvas_item : p_canvas_items) { CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); if (se) { + if (!transform_stored) { + original_transform = canvas_item->get_global_transform(); + transform_stored = true; + } + 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()) { @@ -1000,14 +1021,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; @@ -1100,14 +1115,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) { @@ -1195,7 +1204,7 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve } snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; - drag_type = DRAG_NONE; + _reset_drag(); viewport->update(); return true; } @@ -1286,10 +1295,12 @@ void CanvasItemEditor::_pan_callback(Vector2 p_scroll_vec) { } void CanvasItemEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) { - zoom_widget->set_zoom_by_increments(-1, p_alt); - if (!Math::is_equal_approx(p_scroll_vec.y, (real_t)1.0)) { + int scroll_sign = (int)SIGN(p_scroll_vec.y); + // Inverted so that scrolling up (-1) zooms in, scrolling down (+1) zooms out. + zoom_widget->set_zoom_by_increments(-scroll_sign, p_alt); + if (!Math::is_equal_approx(ABS(p_scroll_vec.y), (real_t)1.0)) { // Handle high-precision (analog) scrolling. - zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * p_scroll_vec.y + 1.f)); + zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * ABS(p_scroll_vec.y) + 1.f)); } _zoom_on_position(zoom_widget->get_zoom(), p_origin); } @@ -1361,14 +1372,14 @@ 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; + _reset_drag(); viewport->update(); return true; } @@ -1444,14 +1455,14 @@ 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; + _reset_drag(); viewport->update(); return true; } @@ -1606,14 +1617,14 @@ 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; + _reset_drag(); viewport->update(); return true; } @@ -1812,7 +1823,7 @@ 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; + _reset_drag(); viewport->update(); return true; } @@ -1822,7 +1833,7 @@ 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; + _reset_drag(); viewport->update(); return true; } @@ -1951,7 +1962,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { _insert_animation_keys(false, false, true, true); } - drag_type = DRAG_NONE; + _reset_drag(); viewport->update(); return true; } @@ -1959,7 +1970,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { // 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; + _reset_drag(); viewport->update(); return true; } @@ -2084,7 +2095,7 @@ 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; + _reset_drag(); viewport->update(); return true; } @@ -2094,7 +2105,7 @@ 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; + _reset_drag(); viewport->update(); return true; } @@ -2201,7 +2212,7 @@ 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(); return true; @@ -2273,7 +2284,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; @@ -2352,7 +2363,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()) { @@ -2403,14 +2414,14 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { } } - drag_type = DRAG_NONE; + _reset_drag(); viewport->update(); return true; } if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) { // Cancel box selection - drag_type = DRAG_NONE; + _reset_drag(); viewport->update(); return true; } @@ -2669,7 +2680,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); @@ -2712,9 +2723,9 @@ void CanvasItemEditor::_draw_guides() { Color guide_color = EditorSettings::get_singleton()->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; @@ -2722,10 +2733,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; @@ -2735,7 +2744,7 @@ 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; @@ -2743,16 +2752,18 @@ void CanvasItemEditor::_draw_guides() { 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); + 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); + 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)); } } @@ -2784,10 +2795,10 @@ void CanvasItemEditor::_draw_rulers() { 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) { @@ -2947,6 +2958,9 @@ 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")); Color font_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); @@ -2963,41 +2977,72 @@ 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(); 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(); h_angle_text_pos.x = CLAMP(end.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width); @@ -3014,33 +3059,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) { @@ -3049,17 +3069,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 { @@ -3605,7 +3629,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); @@ -3626,6 +3650,67 @@ 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::rad2deg(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); @@ -3675,7 +3760,7 @@ void CanvasItemEditor::_draw_viewport() { // Update the transform transform = Transform2D(); transform.scale_basis(Size2(zoom, zoom)); - transform.elements[2] = -view_offset * zoom; + transform.columns[2] = -view_offset * zoom; EditorNode::get_singleton()->get_scene_root()->set_global_canvas_transform(transform); // hide/show buttons depending on the selection @@ -3737,6 +3822,7 @@ void CanvasItemEditor::_draw_viewport() { _draw_smart_snapping(); _draw_focus(); _draw_hover(); + _draw_transform_message(); } void CanvasItemEditor::update_viewport() { @@ -3781,7 +3867,7 @@ void CanvasItemEditor::_update_editor_settings() { 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(); + 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(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); pan_speed = int(EditorSettings::get_singleton()->get("editors/panning/2d_editor_pan_speed")); @@ -3901,15 +3987,19 @@ void CanvasItemEditor::_notification(int p_what) { void CanvasItemEditor::_selection_changed() { if (!selected_from_canvas) { - drag_type = DRAG_NONE; + _reset_drag(); } selected_from_canvas = false; } +void CanvasItemEditor::set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo) { + undo_redo = p_undo_redo; +} + void CanvasItemEditor::edit(CanvasItem *p_canvas_item) { Array selection = editor_selection->get_selected_nodes(); if (selection.size() != 1 || Object::cast_to<Node>(selection[0]) != p_canvas_item) { - drag_type = DRAG_NONE; + _reset_drag(); // Clear the selection editor_selection->clear(); //_clear_canvas_items(); @@ -3917,18 +4007,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; @@ -4101,8 +4179,10 @@ void CanvasItemEditor::_button_tool_select(int p_index) { } void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) { - const 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()) { @@ -4117,13 +4197,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Node2D *n2d = Object::cast_to<Node2D>(canvas_item); 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()) { @@ -4149,13 +4229,13 @@ 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); } } } @@ -4165,16 +4245,17 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Control *ctrl = Object::cast_to<Control>(canvas_item); 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); } } } + te->commit_insert_queue(); } void CanvasItemEditor::_update_override_camera_button(bool p_game_running) { @@ -4412,7 +4493,7 @@ void CanvasItemEditor::_popup_callback(int p_op) { case ANIM_COPY_POSE: { pose_clipboard.clear(); - const 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); @@ -4458,7 +4539,7 @@ 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); @@ -4527,7 +4608,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)")); @@ -4575,7 +4656,7 @@ void CanvasItemEditor::_focus_selection(int p_op) { Rect2 rect; int count = 0; - const 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) { @@ -4630,6 +4711,11 @@ void CanvasItemEditor::_focus_selection(int p_op) { } } +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); @@ -4850,11 +4936,11 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) { 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); } void CanvasItemEditor::add_control_to_left_panel(Control *p_control) { @@ -4888,14 +4974,6 @@ CanvasItemEditor::CanvasItemEditor() { 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; snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; @@ -4908,12 +4986,17 @@ CanvasItemEditor::CanvasItemEditor() { 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(this, "_update_override_camera_button"), make_binds(true)); - EditorNode::get_singleton()->call_deferred(SNAME("connect"), "stop_pressed", Callable(this, "_update_override_camera_button"), make_binds(false)); + EditorNode::get_singleton()->call_deferred(SNAME("connect"), "play_pressed", Callable(this, "_update_override_camera_button").bind(true)); + EditorNode::get_singleton()->call_deferred(SNAME("connect"), "stop_pressed", Callable(this, "_update_override_camera_button").bind(false)); - hb = memnew(HBoxContainer); - add_child(hb); - hb->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + // A fluid container for all toolbars. + HFlowContainer *main_flow = memnew(HFlowContainer); + add_child(main_flow); + + // 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); @@ -4938,12 +5021,44 @@ CanvasItemEditor::CanvasItemEditor() { 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->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)); + // 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 | Key::KEY_0), int32_t(Key::KP_1), int32_t(KeyModifierMask::CMD | 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); @@ -4955,7 +5070,7 @@ CanvasItemEditor::CanvasItemEditor() { 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)); @@ -4978,87 +5093,87 @@ CanvasItemEditor::CanvasItemEditor() { // 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.")); - 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")); 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")); 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.")); - 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->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_LIST_SELECT)); list_select_button->set_tooltip(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->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_EDIT_PIVOT)); pivot_button->set_tooltip(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.")); 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")); - 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.")); @@ -5067,7 +5182,7 @@ CanvasItemEditor::CanvasItemEditor() { 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.")); @@ -5076,7 +5191,7 @@ CanvasItemEditor::CanvasItemEditor() { 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_switch_on_hover(true); @@ -5105,46 +5220,46 @@ CanvasItemEditor::CanvasItemEditor() { 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->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(LOCK_SELECTED)); lock_button->set_tooltip(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)); 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)); + main_menu_hbox->add_child(unlock_button); + unlock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNLOCK_SELECTED)); unlock_button->set_tooltip(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)); 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(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)); 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(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)); - 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); + main_menu_hbox->add_child(skeleton_menu); skeleton_menu->set_tooltip(TTR("Skeleton Options")); skeleton_menu->set_switch_on_hover(true); @@ -5155,24 +5270,25 @@ CanvasItemEditor::CanvasItemEditor() { 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->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); @@ -5204,20 +5320,17 @@ CanvasItemEditor::CanvasItemEditor() { 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)); + 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. - hb->add_child(context_menu_container); - _update_context_menu_stylebox(); + // 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(); @@ -5226,7 +5339,7 @@ CanvasItemEditor::CanvasItemEditor() { 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->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_POS)); key_loc_button->set_tooltip(TTR("Translation mask for inserting keys.")); animation_hb->add_child(key_loc_button); @@ -5235,7 +5348,7 @@ CanvasItemEditor::CanvasItemEditor() { 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->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_ROT)); key_rot_button->set_tooltip(TTR("Rotation mask for inserting keys.")); animation_hb->add_child(key_rot_button); @@ -5243,14 +5356,14 @@ CanvasItemEditor::CanvasItemEditor() { 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->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_SCALE)); key_scale_button->set_tooltip(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->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_KEY)); key_insert_button->set_tooltip(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); @@ -5291,7 +5404,7 @@ CanvasItemEditor::CanvasItemEditor() { 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); @@ -5305,21 +5418,6 @@ CanvasItemEditor::CanvasItemEditor() { // 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_shortcut_input(true); // Update the menus' checkboxes @@ -5329,7 +5427,7 @@ CanvasItemEditor::CanvasItemEditor() { CanvasItemEditor *CanvasItemEditor::singleton = nullptr; void CanvasItemEditorPlugin::edit(Object *p_object) { - canvas_item_editor->set_undo_redo(&get_undo_redo()); + canvas_item_editor->set_undo_redo(EditorNode::get_undo_redo()); canvas_item_editor->edit(Object::cast_to<CanvasItem>(p_object)); } @@ -5362,7 +5460,7 @@ CanvasItemEditorPlugin::CanvasItemEditorPlugin() { canvas_item_editor = memnew(CanvasItemEditor); canvas_item_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); EditorNode::get_singleton()->get_main_control()->add_child(canvas_item_editor); - canvas_item_editor->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + canvas_item_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); canvas_item_editor->hide(); } @@ -5401,7 +5499,7 @@ 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)); @@ -5479,37 +5577,37 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String & } child->set_name(name); - Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(ResourceCache::get(path))); + 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", EditorNode::get_singleton()->get_edited_scene()); - editor_data->get_undo_redo().add_do_reference(child); - editor_data->get_undo_redo().add_undo_method(parent, "remove_child", child); + editor_data->get_undo_redo()->add_do_method(parent, "add_child", child, true); + editor_data->get_undo_redo()->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + editor_data->get_undo_redo()->add_do_reference(child); + editor_data->get_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(EditorNode::get_singleton(), "set_edited_scene", child); - editor_data->get_undo_redo().add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); - editor_data->get_undo_redo().add_do_reference(child); - editor_data->get_undo_redo().add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr); + editor_data->get_undo_redo()->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child); + editor_data->get_undo_redo()->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene()); + editor_data->get_undo_redo()->add_do_reference(child); + editor_data->get_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", EditorNode::get_singleton()->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(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + editor_data->get_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); + editor_data->get_undo_redo()->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); } if (Object::cast_to<TouchScreenButton>(child) || Object::cast_to<TextureButton>(child)) { - editor_data->get_undo_redo().add_do_property(child, "texture_normal", texture); + editor_data->get_undo_redo()->add_do_property(child, "texture_normal", texture); } else { - editor_data->get_undo_redo().add_do_property(child, "texture", texture); + editor_data->get_undo_redo()->add_do_property(child, "texture", texture); } // make visible for certain node type 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); + editor_data->get_undo_redo()->add_do_property(child, "rect_size", texture_size); } else if (Object::cast_to<Polygon2D>(child)) { Size2 texture_size = texture->get_size(); Vector<Vector2> list = { @@ -5518,7 +5616,7 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String & Vector2(texture_size.width, texture_size.height), Vector2(0, texture_size.height) }; - editor_data->get_undo_redo().add_do_property(child, "polygon", list); + editor_data->get_undo_redo()->add_do_property(child, "polygon", list); } // Compute the global position @@ -5527,7 +5625,7 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String & // 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); + editor_data->get_undo_redo()->add_do_method(child, "set_global_position", target_position); } bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) { @@ -5552,15 +5650,15 @@ 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, true); - editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", 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); + editor_data->get_undo_redo()->add_do_method(parent, "add_child", instantiated_scene, true); + editor_data->get_undo_redo()->add_do_method(instantiated_scene, "set_owner", 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); 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", edited_scene->get_path_to(parent), path, new_name); - editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)) + "/" + new_name)); + editor_data->get_undo_redo()->add_do_method(ed, "live_debug_instance_node", edited_scene->get_path_to(parent), path, new_name); + editor_data->get_undo_redo()->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)) + "/" + new_name)); CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene); if (instance_ci) { @@ -5574,7 +5672,7 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons // Preserve instance position of the original scene. target_pos += instance_ci->_edit_get_position(); - editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_position", target_pos); + editor_data->get_undo_redo()->add_do_method(instantiated_scene, "set_position", target_pos); } return true; @@ -5592,11 +5690,11 @@ void CanvasItemEditorViewport::_perform_drop_data() { Vector<String> error_files; - editor_data->get_undo_redo().create_action(TTR("Create Node")); + editor_data->get_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; } @@ -5623,7 +5721,7 @@ void CanvasItemEditorViewport::_perform_drop_data() { } } - editor_data->get_undo_redo().commit_action(); + editor_data->get_undo_redo()->commit_action(); if (error_files.size() > 0) { String files_str; @@ -5651,7 +5749,7 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian for (int i = 0; i < files.size(); i++) { // Check if dragged files with texture or scene extension can be created at least once. if (texture_extensions.find(files[i].get_extension()) || scene_extensions.find(files[i].get_extension())) { - RES res = ResourceLoader::load(files[i]); + Ref<Resource> res = ResourceLoader::load(files[i]); if (res.is_null()) { continue; } @@ -5845,7 +5943,7 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_it 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 26852ea8ed..2ca495e5d6 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -45,6 +45,7 @@ class EditorData; class CanvasItemEditorViewport; class ViewPanner; +class EditorUndoRedoManager; class CanvasItemEditorSelectedItem : public Object { GDCLASS(CanvasItemEditorSelectedItem, Object); @@ -180,7 +181,7 @@ private: GRID_VISIBILITY_HIDE, }; - bool selection_menu_additive_selection; + bool selection_menu_additive_selection = false; Tool tool = TOOL_SELECT; Control *viewport = nullptr; @@ -188,11 +189,10 @@ private: HScrollBar *h_scroll = nullptr; VScrollBar *v_scroll = nullptr; - HBoxContainer *hb = 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 = nullptr; - HBoxContainer *hbc_context_menu = nullptr; + PanelContainer *context_menu_panel = nullptr; + HBoxContainer *context_menu_hbox = nullptr; Transform2D transform; GridVisibility grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING; @@ -204,20 +204,20 @@ private: bool show_edit_locks = true; bool show_transformation_gizmos = true; - real_t zoom; + real_t zoom = 1.0; Point2 view_offset; Point2 previous_update_view_offset; bool selected_from_canvas = false; Point2 grid_offset; - Point2 grid_step; - int primary_grid_steps; - int grid_step_multiplier; + 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; - real_t snap_rotation_offset; - real_t snap_scale_step; + real_t snap_rotation_step = Math::deg2rad(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; @@ -241,7 +241,7 @@ private: bool pan_pressed = false; bool ruler_tool_active = false; - Point2 ruler_tool_origin = Point2(); + Point2 ruler_tool_origin; Point2 node_create_position; MenuOption last_option; @@ -283,7 +283,7 @@ private: } }; - Map<BoneKey, BoneList> bone_list; + HashMap<BoneKey, BoneList> bone_list; struct PoseClipboard { Vector2 pos; @@ -346,7 +346,8 @@ private: 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; @@ -400,7 +401,7 @@ private: void _prepare_grid_menu(); void _on_grid_menu_id_pressed(int p_id); - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; List<CanvasItem *> _get_edited_canvas_items(bool retrieve_locked = false, bool remove_canvas_item_if_parent_in_selection = true); Rect2 _get_encompassing_rect_from_list(List<CanvasItem *> p_list); @@ -433,6 +434,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(); @@ -453,8 +455,8 @@ private: void _update_cursor(); void _selection_changed(); - void _focus_selection(int p_op); + void _reset_drag(); SnapTarget snap_target[2]; Transform2D snap_transform; @@ -492,8 +494,6 @@ private: HSplitContainer *right_panel_split = nullptr; VSplitContainer *bottom_split = nullptr; - void _update_context_menu_stylebox(); - void _set_owner_for_node_and_children(Node *p_node, Node *p_owner); friend class CanvasItemEditorPlugin; @@ -503,8 +503,6 @@ protected: static void _bind_methods(); - HBoxContainer *get_panel_hb() { return hb; } - static CanvasItemEditor *singleton; public: @@ -550,7 +548,7 @@ 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 set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo); void edit(CanvasItem *p_canvas_item); void focus_selection(); @@ -633,4 +631,4 @@ public: ~CanvasItemEditorViewport(); }; -#endif //CANVAS_ITEM_EDITOR_PLUGIN_H +#endif // CANVAS_ITEM_EDITOR_PLUGIN_H diff --git a/editor/plugins/ray_cast_2d_editor_plugin.cpp b/editor/plugins/cast_2d_editor_plugin.cpp index 6f247a37ef..a8d255f997 100644 --- a/editor/plugins/ray_cast_2d_editor_plugin.cpp +++ b/editor/plugins/cast_2d_editor_plugin.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* ray_cast_2d_editor_plugin.cpp */ +/* cast_2d_editor_plugin.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,30 +28,33 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "ray_cast_2d_editor_plugin.h" +#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 RayCast2DEditor::_notification(int p_what) { +void Cast2DEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - get_tree()->connect("node_removed", callable_mp(this, &RayCast2DEditor::_node_removed)); + get_tree()->connect("node_removed", callable_mp(this, &Cast2DEditor::_node_removed)); } break; case NOTIFICATION_EXIT_TREE: { - get_tree()->disconnect("node_removed", callable_mp(this, &RayCast2DEditor::_node_removed)); + get_tree()->disconnect("node_removed", callable_mp(this, &Cast2DEditor::_node_removed)); } break; } } -void RayCast2DEditor::_node_removed(Node *p_node) { +void Cast2DEditor::_node_removed(Node *p_node) { if (p_node == node) { node = nullptr; } } -bool RayCast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { +bool Cast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { if (!node || !node->is_visible_in_tree()) { return false; } @@ -60,10 +63,12 @@ bool RayCast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { 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(node->get_target_position()).distance_to(mb->get_position()) < 8) { + if (xform.xform(target_position).distance_to(mb->get_position()) < 8) { pressed = true; - original_target_position = node->get_target_position(); + original_target_position = target_position; return true; } else { @@ -73,9 +78,9 @@ bool RayCast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { } } else if (pressed) { undo_redo->create_action(TTR("Set target_position")); - undo_redo->add_do_method(node, "set_target_position", node->get_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_method(node, "set_target_position", original_target_position); + 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(); @@ -90,7 +95,7 @@ bool RayCast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { 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); + node->set("target_position", point); canvas_item_editor->update_viewport(); node->notify_property_list_changed(); @@ -100,7 +105,7 @@ bool RayCast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return false; } -void RayCast2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { +void Cast2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { if (!node || !node->is_visible_in_tree()) { return; } @@ -108,16 +113,16 @@ void RayCast2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { 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(node->get_target_position()) - handle->get_size() / 2); + p_overlay->draw_texture(handle, gt.xform((Vector2)node->get("target_position")) - handle->get_size() / 2); } -void RayCast2DEditor::edit(Node *p_node) { +void Cast2DEditor::edit(Node2D *p_node) { if (!canvas_item_editor) { canvas_item_editor = CanvasItemEditor::get_singleton(); } - if (p_node) { - node = Object::cast_to<RayCast2D>(p_node); + if (Object::cast_to<RayCast2D>(p_node) || Object::cast_to<ShapeCast2D>(p_node)) { + node = p_node; } else { node = nullptr; } @@ -125,27 +130,27 @@ void RayCast2DEditor::edit(Node *p_node) { canvas_item_editor->update_viewport(); } -RayCast2DEditor::RayCast2DEditor() { +Cast2DEditor::Cast2DEditor() { undo_redo = EditorNode::get_singleton()->get_undo_redo(); } /////////////////////// -void RayCast2DEditorPlugin::edit(Object *p_object) { - ray_cast_2d_editor->edit(Object::cast_to<RayCast2D>(p_object)); +void Cast2DEditorPlugin::edit(Object *p_object) { + cast_2d_editor->edit(Object::cast_to<Node2D>(p_object)); } -bool RayCast2DEditorPlugin::handles(Object *p_object) const { - return Object::cast_to<RayCast2D>(p_object) != nullptr; +bool Cast2DEditorPlugin::handles(Object *p_object) const { + return Object::cast_to<RayCast2D>(p_object) != nullptr || Object::cast_to<ShapeCast2D>(p_object) != nullptr; } -void RayCast2DEditorPlugin::make_visible(bool p_visible) { +void Cast2DEditorPlugin::make_visible(bool p_visible) { if (!p_visible) { edit(nullptr); } } -RayCast2DEditorPlugin::RayCast2DEditorPlugin() { - ray_cast_2d_editor = memnew(RayCast2DEditor); - EditorNode::get_singleton()->get_gui_base()->add_child(ray_cast_2d_editor); +Cast2DEditorPlugin::Cast2DEditorPlugin() { + cast_2d_editor = memnew(Cast2DEditor); + EditorNode::get_singleton()->get_gui_base()->add_child(cast_2d_editor); } diff --git a/editor/plugins/ray_cast_2d_editor_plugin.h b/editor/plugins/cast_2d_editor_plugin.h index 74628da0e4..85ff497bc7 100644 --- a/editor/plugins/ray_cast_2d_editor_plugin.h +++ b/editor/plugins/cast_2d_editor_plugin.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* ray_cast_2d_editor_plugin.h */ +/* cast_2d_editor_plugin.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,20 +28,21 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef RAY_CAST_2D_EDITOR_PLUGIN_H -#define RAY_CAST_2D_EDITOR_PLUGIN_H +#ifndef CAST_2D_EDITOR_PLUGIN_H +#define CAST_2D_EDITOR_PLUGIN_H #include "editor/editor_plugin.h" -#include "scene/2d/ray_cast_2d.h" +#include "scene/2d/node_2d.h" class CanvasItemEditor; +class EditorUndoRedoManager; -class RayCast2DEditor : public Control { - GDCLASS(RayCast2DEditor, Control); +class Cast2DEditor : public Control { + GDCLASS(Cast2DEditor, Control); - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; CanvasItemEditor *canvas_item_editor = nullptr; - RayCast2D *node; + Node2D *node; bool pressed = false; Point2 original_target_position; @@ -53,27 +54,27 @@ protected: public: bool forward_canvas_gui_input(const Ref<InputEvent> &p_event); void forward_canvas_draw_over_viewport(Control *p_overlay); - void edit(Node *p_node); + void edit(Node2D *p_node); - RayCast2DEditor(); + Cast2DEditor(); }; -class RayCast2DEditorPlugin : public EditorPlugin { - GDCLASS(RayCast2DEditorPlugin, EditorPlugin); +class Cast2DEditorPlugin : public EditorPlugin { + GDCLASS(Cast2DEditorPlugin, EditorPlugin); - RayCast2DEditor *ray_cast_2d_editor = nullptr; + Cast2DEditor *cast_2d_editor = nullptr; public: - virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return ray_cast_2d_editor->forward_canvas_gui_input(p_event); } - virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { ray_cast_2d_editor->forward_canvas_draw_over_viewport(p_overlay); } + 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 "RayCast2D"; } + 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; - RayCast2DEditorPlugin(); + Cast2DEditorPlugin(); }; -#endif // RAY_CAST_2D_EDITOR_PLUGIN_H +#endif // CAST_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 af20064a8d..11992ad10e 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.cpp +++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp @@ -33,6 +33,7 @@ #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" diff --git a/editor/plugins/collision_shape_2d_editor_plugin.h b/editor/plugins/collision_shape_2d_editor_plugin.h index da9e9f339f..49e0820ae9 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.h +++ b/editor/plugins/collision_shape_2d_editor_plugin.h @@ -35,6 +35,7 @@ #include "scene/2d/collision_shape_2d.h" class CanvasItemEditor; +class EditorUndoRedoManager; class CollisionShape2DEditor : public Control { GDCLASS(CollisionShape2DEditor, Control); @@ -61,7 +62,7 @@ class CollisionShape2DEditor : public Control { Point2(1, -1), }; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; CanvasItemEditor *canvas_item_editor = nullptr; CollisionShape2D *node = nullptr; @@ -112,4 +113,4 @@ public: ~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 index 5ca8216d4d..2a12dc0e89 100644 --- a/editor/plugins/control_editor_plugin.cpp +++ b/editor/plugins/control_editor_plugin.cpp @@ -31,7 +31,13 @@ #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/separator.h" + +// Inspector controls. void ControlPositioningWarning::_update_warning() { if (!control_node) { @@ -47,7 +53,7 @@ void ControlPositioningWarning::_update_warning() { 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("Container"), SNAME("EditorIcons"))); + 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 { @@ -120,7 +126,7 @@ ControlPositioningWarning::ControlPositioningWarning() { grid->add_child(title_icon); title_label = memnew(Label); - title_label->set_autowrap_mode(Label::AutowrapMode::AUTOWRAP_WORD); + 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); @@ -135,7 +141,7 @@ ControlPositioningWarning::ControlPositioningWarning() { grid->add_child(hint_filler_left); hint_label = memnew(Label); - hint_label->set_autowrap_mode(Label::AutowrapMode::AUTOWRAP_WORD); + 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(); @@ -173,7 +179,7 @@ void EditorPropertyAnchorsPreset::setup(const Vector<String> &p_options) { Vector<String> split_after; split_after.append("Custom"); - split_after.append("PresetWide"); + split_after.append("PresetFullRect"); split_after.append("PresetBottomLeft"); split_after.append("PresetCenter"); @@ -181,24 +187,18 @@ void EditorPropertyAnchorsPreset::setup(const Vector<String> &p_options) { Vector<String> text_split = p_options[i].split(":"); int64_t current_val = text_split[1].to_int(); - String humanized_name = text_split[0]; - if (humanized_name.begins_with("Preset")) { - if (humanized_name == "PresetWide") { - humanized_name = "Full Rect"; - } else { - humanized_name = humanized_name.trim_prefix("Preset"); - humanized_name = humanized_name.capitalize(); - } - - String icon_name = text_split[0].trim_prefix("Preset"); - icon_name = "ControlAlign" + icon_name; + 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(humanized_name); + options->add_item(option_name); } options->set_item_metadata(j, current_val); - if (split_after.has(text_split[0])) { + if (split_after.has(option_name)) { options->add_separator(); j++; } @@ -335,7 +335,7 @@ void EditorPropertySizeFlags::setup(const Vector<String> &p_options, bool p_vert return; } - Map<int, String> flags; + 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(); @@ -452,37 +452,280 @@ bool EditorInspectorPluginControl::parse_property(Object *p_object, const Varian return false; } -void ControlEditorToolbar::_set_anchors_and_offsets_preset(Control::LayoutPreset p_preset) { +// 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("ControlEditorPopupButton"); + 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(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(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(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(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(); - undo_redo->create_action(TTR("Change Anchors and Offsets")); + 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_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_do_property(control, "anchors_preset", preset); undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); } } @@ -493,10 +736,10 @@ void ControlEditorToolbar::_set_anchors_and_offsets_preset(Control::LayoutPreset anchor_mode_button->set_pressed(anchors_mode); } -void ControlEditorToolbar::_set_anchors_and_offsets_to_keep_ratio() { +void ControlEditorToolbar::_anchors_to_current_ratio() { List<Node *> selection = editor_selection->get_selected_node_list(); - undo_redo->create_action(TTR("Change Anchors and Offsets")); + undo_redo->create_action(TTR("Change Anchors, Offsets (Keep Ratio)")); for (Node *E : selection) { Control *control = Object::cast_to<Control>(E); @@ -509,7 +752,7 @@ void ControlEditorToolbar::_set_anchors_and_offsets_to_keep_ratio() { 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->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_"); + 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); @@ -525,44 +768,41 @@ void ControlEditorToolbar::_set_anchors_and_offsets_to_keep_ratio() { undo_redo->commit_action(); } -void ControlEditorToolbar::_set_anchors_preset(Control::LayoutPreset p_preset) { - List<Node *> selection = editor_selection->get_selected_node_list(); +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; + } - 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()); + if (p_status) { + E->set_meta("_edit_use_anchors_", true); + } else { + E->remove_meta("_edit_use_anchors_"); } } - undo_redo->commit_action(); + anchors_mode = p_status; + CanvasItemEditor::get_singleton()->update_viewport(); } -void ControlEditorToolbar::_set_container_h_preset(Control::SizeFlags p_preset) { +void ControlEditorToolbar::_container_flags_selected(int p_flags, bool p_vertical) { List<Node *> selection = editor_selection->get_selected_node_list(); - undo_redo->create_action(TTR("Change Horizontal Size Flags")); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - undo_redo->add_do_method(control, "set_h_size_flags", p_preset); - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - } + if (p_vertical) { + undo_redo->create_action(TTR("Change Vertical Size Flags")); + } else { + undo_redo->create_action(TTR("Change Horizontal Size Flags")); } - undo_redo->commit_action(); -} - -void ControlEditorToolbar::_set_container_v_preset(Control::SizeFlags p_preset) { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Horizontal Size Flags")); for (Node *E : selection) { Control *control = Object::cast_to<Control>(E); if (control) { - undo_redo->add_do_method(control, "set_v_size_flags", p_preset); + 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()); } } @@ -598,400 +838,205 @@ Vector2 ControlEditorToolbar::_position_to_anchor(const Control *p_control, Vect return output; } -void ControlEditorToolbar::_button_toggle_anchor_mode(bool p_status) { - List<Control *> selection = _get_edited_controls(false, false); - 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(); -} - bool ControlEditorToolbar::_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); } -List<Control *> ControlEditorToolbar::_get_edited_controls(bool retrieve_locked, bool remove_controls_if_parent_in_selection) { +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() && (retrieve_locked || !_is_node_locked(control))) { + if (control && control->is_visible_in_tree() && control->get_viewport() == EditorNode::get_singleton()->get_scene_root() && !_is_node_locked(control)) { selection.push_back(control); } } - if (remove_controls_if_parent_in_selection) { - List<Control *> filtered_selection; - for (Control *E : selection) { - if (!selection.find(E->get_parent())) { - filtered_selection.push_back(E); - } - } - return filtered_selection; - } - return selection; } -void ControlEditorToolbar::_popup_callback(int p_op) { - switch (p_op) { - 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 CONTAINERS_H_PRESET_FILL: { - _set_container_h_preset(Control::SIZE_FILL); - } break; - case CONTAINERS_H_PRESET_FILL_EXPAND: { - _set_container_h_preset(Control::SIZE_EXPAND_FILL); - } break; - case CONTAINERS_H_PRESET_SHRINK_BEGIN: { - _set_container_h_preset(Control::SIZE_SHRINK_BEGIN); - } break; - case CONTAINERS_H_PRESET_SHRINK_CENTER: { - _set_container_h_preset(Control::SIZE_SHRINK_CENTER); - } break; - case CONTAINERS_H_PRESET_SHRINK_END: { - _set_container_h_preset(Control::SIZE_SHRINK_END); - } break; - - case CONTAINERS_V_PRESET_FILL: { - _set_container_v_preset(Control::SIZE_FILL); - } break; - case CONTAINERS_V_PRESET_FILL_EXPAND: { - _set_container_v_preset(Control::SIZE_EXPAND_FILL); - } break; - case CONTAINERS_V_PRESET_SHRINK_BEGIN: { - _set_container_v_preset(Control::SIZE_SHRINK_BEGIN); - } break; - case CONTAINERS_V_PRESET_SHRINK_CENTER: { - _set_container_v_preset(Control::SIZE_SHRINK_CENTER); - } break; - case CONTAINERS_V_PRESET_SHRINK_END: { - _set_container_v_preset(Control::SIZE_SHRINK_END); - } break; - } -} - void ControlEditorToolbar::_selection_changed() { - // Update the anchors_mode. - int nb_controls = 0; - int nb_valid_controls = 0; - int nb_anchors_mode = 0; + // 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, + }; - List<Node *> selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); + for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { + Control *control = Object::cast_to<Control>(E.key); if (!control) { continue; } + has_controls = true; - nb_controls++; + if (Object::cast_to<Control>(control->get_parent())) { + has_control_parents = true; + } if (Object::cast_to<Container>(control->get_parent())) { - continue; + 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); - nb_valid_controls++; - if (control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_")) { - nb_anchors_mode++; + // 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); } - anchors_mode = (nb_valid_controls == nb_anchors_mode); - anchor_mode_button->set_pressed(anchors_mode); + // Set container tools visibility. + if (has_controls && (!has_control_parents || has_container_parents)) { + containers_button->set_visible(true); - if (nb_controls > 0) { - set_physics_process(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 { - set_physics_process(false); - set_visible(false); + containers_button->set_visible(false); } } void ControlEditorToolbar::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - anchor_presets_menu->set_icon(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); - - PopupMenu *p = anchor_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("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), 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("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), 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 Current Ratio"), ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO); - p->set_item_tooltip(19, TTR("Adjust anchors and offsets to match the current rect size.")); - - 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("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_PRESET_CENTER_LEFT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_PRESET_CENTER_TOP); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_PRESET_CENTER_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), 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("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_PRESET_VCENTER_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), 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); - + 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"))); - - container_h_presets_menu->set_icon(get_theme_icon(SNAME("Container"), SNAME("EditorIcons"))); - container_v_presets_menu->set_icon(get_theme_icon(SNAME("Container"), SNAME("EditorIcons"))); - - p = container_h_presets_menu->get_popup(); - p->clear(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("Fill"), CONTAINERS_H_PRESET_FILL); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("Fill & Expand"), CONTAINERS_H_PRESET_FILL_EXPAND); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Shrink Begin"), CONTAINERS_H_PRESET_SHRINK_BEGIN); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), CONTAINERS_H_PRESET_SHRINK_CENTER); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Shrink End"), CONTAINERS_H_PRESET_SHRINK_END); - - p = container_v_presets_menu->get_popup(); - p->clear(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("Fill"), CONTAINERS_V_PRESET_FILL); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("Fill & Expand"), CONTAINERS_V_PRESET_FILL_EXPAND); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Shrink Begin"), CONTAINERS_V_PRESET_SHRINK_BEGIN); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), CONTAINERS_V_PRESET_SHRINK_CENTER); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Shrink End"), CONTAINERS_V_PRESET_SHRINK_END); - } break; - - case NOTIFICATION_PHYSICS_PROCESS: { - bool has_control_parents = false; - bool has_container_parents = false; - - // Update the viewport if the canvas_item changes - List<Control *> selection = _get_edited_controls(true); - for (Control *control : selection) { - if (Object::cast_to<Control>(control->get_parent())) { - has_control_parents = true; - } - if (Object::cast_to<Container>(control->get_parent())) { - has_container_parents = true; - } - } - - // Show / Hide the control layout buttons. - if (selection.size() > 0) { - set_visible(true); - - // Toggle anchor and container layout buttons depending on parents of the selected nodes. - // - If there are no control parents, enable everything. - // - If there are container parents, then enable only container buttons. - // - If there are NO container parents, then enable only anchor buttons. - bool enable_anchors = false; - bool enable_containers = false; - if (!has_control_parents) { - enable_anchors = true; - enable_containers = true; - } else if (has_container_parents) { - enable_containers = true; - } else { - enable_anchors = true; - } - - if (enable_anchors) { - anchor_presets_menu->set_visible(true); - anchor_mode_button->set_visible(true); - } else { - anchor_presets_menu->set_visible(false); - anchor_mode_button->set_visible(false); - } - - if (enable_containers) { - container_h_presets_menu->set_visible(true); - container_v_presets_menu->set_visible(true); - } else { - container_h_presets_menu->set_visible(false); - container_v_presets_menu->set_visible(false); - } - } else { - set_visible(false); - } + containers_button->set_icon(get_theme_icon(SNAME("ContainerLayout"), SNAME("EditorIcons"))); } break; } } ControlEditorToolbar::ControlEditorToolbar() { - anchor_presets_menu = memnew(MenuButton); - anchor_presets_menu->set_shortcut_context(this); - anchor_presets_menu->set_text(TTR("Anchors")); - anchor_presets_menu->set_tooltip(TTR("Presets for the anchor and offset values of a Control node.")); - add_child(anchor_presets_menu); - anchor_presets_menu->set_switch_on_hover(true); + add_child(memnew(VSeparator)); + + // Anchor and offset tools. + anchors_button = memnew(ControlEditorPopupButton); + anchors_button->set_tooltip(TTR("Presets for the anchor and offset values of a Control node.")); + add_child(anchors_button); - PopupMenu *p = anchor_presets_menu->get_popup(); - p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + 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_popup = memnew(PopupMenu); - p->add_child(anchors_popup); - anchors_popup->set_name("Anchors"); - anchors_popup->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + 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(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(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::_button_toggle_anchor_mode)); - - add_child(memnew(VSeparator)); - - container_h_presets_menu = memnew(MenuButton); - container_h_presets_menu->set_shortcut_context(this); - container_h_presets_menu->set_text(TTR("Horizontal")); - container_h_presets_menu->set_tooltip(TTR("Horizontal sizing setting for children of a Container node.")); - add_child(container_h_presets_menu); - container_h_presets_menu->set_switch_on_hover(true); - - p = container_h_presets_menu->get_popup(); - p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); - - container_v_presets_menu = memnew(MenuButton); - container_v_presets_menu->set_shortcut_context(this); - container_v_presets_menu->set_text(TTR("Vertical")); - container_v_presets_menu->set_tooltip(TTR("Vertical sizing setting for children of a Container node.")); - add_child(container_v_presets_menu); - container_v_presets_menu->set_switch_on_hover(true); - - p = container_v_presets_menu->get_popup(); - p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); - + anchor_mode_button->connect("toggled", callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled)); + + // Container tools. + containers_button = memnew(ControlEditorPopupButton); + containers_button->set_tooltip(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. undo_redo = EditorNode::get_singleton()->get_undo_redo(); editor_selection = EditorNode::get_singleton()->get_editor_selection(); editor_selection->add_editor_plugin(this); @@ -1002,6 +1047,8 @@ ControlEditorToolbar::ControlEditorToolbar() { ControlEditorToolbar *ControlEditorToolbar::singleton = nullptr; +// Editor plugin. + ControlEditorPlugin::ControlEditorPlugin() { toolbar = memnew(ControlEditorToolbar); toolbar->hide(); diff --git a/editor/plugins/control_editor_plugin.h b/editor/plugins/control_editor_plugin.h index 96451f7dcf..584d05aab0 100644 --- a/editor/plugins/control_editor_plugin.h +++ b/editor/plugins/control_editor_plugin.h @@ -33,14 +33,20 @@ #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 EditorUndoRedoManager; + +// Inspector controls. class ControlPositioningWarning : public MarginContainer { GDCLASS(ControlPositioningWarning, MarginContainer); @@ -125,102 +131,101 @@ public: 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; }; +// 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; + + 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); - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; EditorSelection *editor_selection = nullptr; - enum MenuOption { - 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 Presets are not currently in use. - 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, - - CONTAINERS_H_PRESET_FILL, - CONTAINERS_H_PRESET_FILL_EXPAND, - CONTAINERS_H_PRESET_SHRINK_BEGIN, - CONTAINERS_H_PRESET_SHRINK_CENTER, - CONTAINERS_H_PRESET_SHRINK_END, - CONTAINERS_V_PRESET_FILL, - CONTAINERS_V_PRESET_FILL_EXPAND, - CONTAINERS_V_PRESET_SHRINK_BEGIN, - CONTAINERS_V_PRESET_SHRINK_CENTER, - CONTAINERS_V_PRESET_SHRINK_END, - }; - - MenuButton *anchor_presets_menu = nullptr; - PopupMenu *anchors_popup = nullptr; - MenuButton *container_h_presets_menu = nullptr; - MenuButton *container_v_presets_menu = 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 _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 _set_container_h_preset(Control::SizeFlags p_preset); - void _set_container_v_preset(Control::SizeFlags p_preset); + 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 _anchor_to_position(const Control *p_control, Vector2 anchor); Vector2 _position_to_anchor(const Control *p_control, Vector2 position); - - void _button_toggle_anchor_mode(bool p_status); - bool _is_node_locked(const Node *p_node); - List<Control *> _get_edited_controls(bool retrieve_locked = false, bool remove_controls_if_parent_in_selection = true); - void _popup_callback(int p_op); + List<Control *> _get_edited_controls(); void _selection_changed(); protected: @@ -236,6 +241,7 @@ public: ControlEditorToolbar(); }; +// Editor plugin. class ControlEditorPlugin : public EditorPlugin { GDCLASS(ControlEditorPlugin, EditorPlugin); @@ -247,4 +253,4 @@ public: ControlEditorPlugin(); }; -#endif //CONTROL_EDITOR_PLUGIN_H +#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 79025041d3..e20d298195 100644 --- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_2d_editor_plugin.cpp @@ -34,6 +34,7 @@ #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 "scene/2d/cpu_particles_2d.h" #include "scene/gui/separator.h" #include "scene/resources/particles_material.h" @@ -257,7 +258,7 @@ CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() { 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 cc59bc924f..06ca208463 100644 --- a/editor/plugins/cpu_particles_2d_editor_plugin.h +++ b/editor/plugins/cpu_particles_2d_editor_plugin.h @@ -39,6 +39,7 @@ class EditorPlugin; class SpinBox; class EditorFileDialog; +class EditorUndoRedoManager; class CPUParticles2DEditorPlugin : public EditorPlugin { GDCLASS(CPUParticles2DEditorPlugin, EditorPlugin); @@ -70,7 +71,7 @@ class CPUParticles2DEditorPlugin : public EditorPlugin { String source_emission_file; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; void _file_selected(const String &p_file); void _menu_callback(int p_idx); void _generate_emission_mask(); diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.h b/editor/plugins/cpu_particles_3d_editor_plugin.h index 70f2da4b2d..f38349985c 100644 --- a/editor/plugins/cpu_particles_3d_editor_plugin.h +++ b/editor/plugins/cpu_particles_3d_editor_plugin.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef CPU_PARTICLES_EDITOR_PLUGIN_H -#define CPU_PARTICLES_EDITOR_PLUGIN_H +#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" @@ -78,4 +78,4 @@ public: ~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 6d1a86765a..013a9f10a4 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -36,6 +36,7 @@ #include "core/os/keyboard.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" CurveEditor::CurveEditor() { _selected_point = -1; @@ -138,14 +139,14 @@ 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::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); + 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); // 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(); + ur->commit_action(); _has_undo_data = false; } @@ -300,13 +301,13 @@ 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")); + Ref<EditorUndoRedoManager> &ur = EditorNode::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->add_do_method(&curve, "_set_data", curve.get_data()); + ur->add_undo_method(&curve, "_set_data", previous_data); - ur.commit_action(); + ur->commit_action(); } void CurveEditor::_curve_changed() { @@ -434,8 +435,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")); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); + ur->create_action(TTR("Remove Curve Point")); Vector2 point_pos = get_world_pos(pos); if (point_pos.y < 0.0) { @@ -448,22 +449,22 @@ 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->add_do_method(*_curve_ref, "add_point", point_pos); + ur->add_undo_method(*_curve_ref, "remove_point", i); - ur.commit_action(); + ur->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")); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); + ur->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); + 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); if (index == _selected_point) { set_selected_point(-1); @@ -473,14 +474,14 @@ void CurveEditor::remove_point(int index) { set_hover_point_index(-1); } - ur.commit_action(); + ur->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")); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); + ur->create_action(TTR("Toggle Curve Linear Tangent")); if (tangent == TANGENT_NONE) { tangent = _selected_tangent; @@ -492,8 +493,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); + 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); } else { bool is_linear = _curve_ref->get_point_right_mode(_selected_point) == Curve::TANGENT_LINEAR; @@ -501,11 +502,11 @@ 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); + 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); } - ur.commit_action(); + ur->commit_action(); } void CurveEditor::set_selected_point(int index) { @@ -542,11 +543,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; } @@ -751,12 +752,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, -1, font_size, 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, -1, font_size, text_color); } } @@ -840,9 +842,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/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index 8ea50c4529..40e7dfead2 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -35,11 +35,12 @@ #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(MenuButton *p_debug_menu) { +DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) { EditorDebuggerServer::initialize(); ED_SHORTCUT("debugger/step_into", TTR("Step Into"), Key::F11); @@ -60,27 +61,29 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(MenuButton *p_debug_menu) { // 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(-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(-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(-1, + debug_menu->add_separator(); + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_collision_shapes", TTR("Visible Collision Shapes")), RUN_DEBUG_COLLISONS); + 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(-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(-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(-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.")); // Multi-instance, start/stop @@ -88,9 +91,9 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(MenuButton *p_debug_menu) { 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"); + debug_menu->add_child(instances_menu); + debug_menu->add_separator(); + debug_menu->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); @@ -102,7 +105,7 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(MenuButton *p_debug_menu) { instances_menu->set_item_metadata(3, 4); 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() { @@ -121,7 +124,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(); @@ -129,39 +132,45 @@ 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); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEBUG_COLLISONS)); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEBUG_COLLISONS), !ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_collisons", !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); @@ -182,6 +191,7 @@ 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_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); @@ -196,6 +206,9 @@ void DebuggerEditorPlugin::_update_debug_options() { if (check_debug_collisions) { _menu_option(RUN_DEBUG_COLLISONS); } + if (check_debug_paths) { + _menu_option(RUN_DEBUG_PATHS); + } if (check_debug_navigation) { _menu_option(RUN_DEBUG_NAVIGATION); } diff --git a/editor/plugins/debugger_editor_plugin.h b/editor/plugins/debugger_editor_plugin.h index 10e1a27933..d8871128c3 100644 --- a/editor/plugins/debugger_editor_plugin.h +++ b/editor/plugins/debugger_editor_plugin.h @@ -41,7 +41,7 @@ class DebuggerEditorPlugin : public EditorPlugin { GDCLASS(DebuggerEditorPlugin, EditorPlugin); private: - MenuButton *debug_menu = nullptr; + PopupMenu *debug_menu = nullptr; EditorFileServer *file_server = nullptr; PopupMenu *instances_menu = nullptr; @@ -49,6 +49,7 @@ private: RUN_FILE_SERVER, RUN_LIVE_DEBUG, RUN_DEBUG_COLLISONS, + RUN_DEBUG_PATHS, RUN_DEBUG_NAVIGATION, RUN_DEPLOY_REMOTE_DEBUG, RUN_RELOAD_SCRIPTS, @@ -63,7 +64,7 @@ public: virtual String get_name() const override { return "Debugger"; } bool has_main_screen() const override { return false; } - DebuggerEditorPlugin(MenuButton *p_menu); + DebuggerEditorPlugin(PopupMenu *p_menu); ~DebuggerEditorPlugin(); }; diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index a160ca463b..0196214ceb 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -79,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()) { @@ -127,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() { @@ -145,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()) { @@ -171,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() { @@ -194,7 +185,7 @@ 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()) { @@ -239,13 +230,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 { @@ -261,7 +248,7 @@ 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); } @@ -282,11 +269,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>(); @@ -316,14 +300,14 @@ 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_ONESHOT); preview_done.wait(); @@ -336,9 +320,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>(); @@ -467,7 +449,7 @@ 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>(); @@ -481,8 +463,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)) { @@ -591,13 +573,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() { @@ -609,7 +586,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>()); @@ -622,7 +599,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(); @@ -676,12 +653,10 @@ 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; + return ImageTexture::create_from_image(image); } EditorAudioStreamPreviewPlugin::EditorAudioStreamPreviewPlugin() { @@ -703,7 +678,7 @@ bool EditorMeshPreviewPlugin::handles(const String &p_type) const { 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>()); @@ -727,7 +702,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_ONESHOT); preview_done.wait(); @@ -746,12 +721,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() { @@ -812,19 +784,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😀"; @@ -836,20 +801,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_ONESHOT); preview_done.wait(); @@ -868,16 +831,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>(); @@ -917,18 +876,14 @@ bool EditorGradientPreviewPlugin::generate_small_preview_automatically() const { return true; } -Ref<Texture2D> EditorGradientPreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +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); - - Ref<ImageTexture> itex; - itex.instantiate(); - itex->create_from_image(ptex->get_image()); - return itex; + return ImageTexture::create_from_image(ptex->get_image()); } return Ref<Texture2D>(); } diff --git a/editor/plugins/editor_preview_plugins.h b/editor/plugins/editor_preview_plugins.h index 73eb90dd86..163cfe79f9 100644 --- a/editor/plugins/editor_preview_plugins.h +++ b/editor/plugins/editor_preview_plugins.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef EDITORPREVIEWPLUGINS_H -#define EDITORPREVIEWPLUGINS_H +#ifndef EDITOR_PREVIEW_PLUGINS_H +#define EDITOR_PREVIEW_PLUGINS_H #include "core/templates/safe_refcount.h" #include "editor/editor_resource_preview.h" @@ -42,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(); }; @@ -53,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(); }; @@ -64,7 +64,7 @@ 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(); }; @@ -72,7 +72,7 @@ public: class EditorPackedScenePreviewPlugin : public 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(const Ref<Resource> &p_from, const Size2 &p_size) const; virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size) const; EditorPackedScenePreviewPlugin(); @@ -99,7 +99,7 @@ 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(); @@ -108,7 +108,7 @@ public: class EditorScriptPreviewPlugin : public 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(const Ref<Resource> &p_from, const Size2 &p_size) const; EditorScriptPreviewPlugin(); }; @@ -116,7 +116,7 @@ public: class EditorAudioStreamPreviewPlugin : public 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(const Ref<Resource> &p_from, const Size2 &p_size) const; EditorAudioStreamPreviewPlugin(); }; @@ -140,7 +140,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(); @@ -160,7 +160,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(); @@ -177,7 +177,7 @@ 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(); @@ -189,8 +189,9 @@ class EditorGradientPreviewPlugin : 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; EditorGradientPreviewPlugin(); }; -#endif // EDITORPREVIEWPLUGINS_H + +#endif // EDITOR_PREVIEW_PLUGINS_H diff --git a/editor/plugins/audio_stream_editor_plugin.h b/editor/plugins/editor_resource_conversion_plugin.cpp index 0d927bddd5..91394dbac7 100644 --- a/editor/plugins/audio_stream_editor_plugin.h +++ b/editor/plugins/editor_resource_conversion_plugin.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* audio_stream_editor_plugin.h */ +/* editor_resource_conversion_plugin.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,64 +28,37 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef AUDIO_STREAM_EDITOR_PLUGIN_H -#define AUDIO_STREAM_EDITOR_PLUGIN_H +#include "editor_resource_conversion_plugin.h" -#include "editor/editor_plugin.h" -#include "scene/audio/audio_stream_player.h" -#include "scene/gui/color_rect.h" -#include "scene/resources/texture.h" +void EditorResourceConversionPlugin::_bind_methods() { + GDVIRTUAL_BIND(_converts_to); + GDVIRTUAL_BIND(_handles, "resource"); + GDVIRTUAL_BIND(_convert, "resource"); +} -class AudioStreamEditor : public ColorRect { - GDCLASS(AudioStreamEditor, ColorRect); +String EditorResourceConversionPlugin::converts_to() const { + String ret; + if (GDVIRTUAL_CALL(_converts_to, ret)) { + return ret; + } - Ref<AudioStream> stream; - AudioStreamPlayer *_player = nullptr; - ColorRect *_preview = nullptr; - Control *_indicator = nullptr; - Label *_current_label = nullptr; - Label *_duration_label = nullptr; + return ""; +} - Button *_play_button = nullptr; - Button *_stop_button = nullptr; +bool EditorResourceConversionPlugin::handles(const Ref<Resource> &p_resource) const { + bool ret; + if (GDVIRTUAL_CALL(_handles, p_resource, ret)) { + return ret; + } - float _current = 0; - bool _dragging = false; - bool _pausing = false; + return false; +} - void _audio_changed(); +Ref<Resource> EditorResourceConversionPlugin::convert(const Ref<Resource> &p_resource) const { + Ref<Resource> ret; + if (GDVIRTUAL_CALL(_convert, p_resource, ret)) { + return ret; + } -protected: - void _notification(int p_what); - void _preview_changed(ObjectID p_which); - void _play(); - void _stop(); - void _on_finished(); - void _draw_preview(); - void _draw_indicator(); - void _on_input_indicator(Ref<InputEvent> p_event); - void _seek_to(real_t p_x); - static void _bind_methods(); - -public: - void edit(Ref<AudioStream> p_stream); - AudioStreamEditor(); -}; - -class AudioStreamEditorPlugin : public EditorPlugin { - GDCLASS(AudioStreamEditorPlugin, EditorPlugin); - - AudioStreamEditor *audio_editor = nullptr; - -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(); - ~AudioStreamEditorPlugin(); -}; - -#endif // AUDIO_STREAM_EDITOR_PLUGIN_H + return Ref<Resource>(); +} diff --git a/editor/plugins/font_editor_plugin.h b/editor/plugins/editor_resource_conversion_plugin.h index 3f0700d880..34b0837383 100644 --- a/editor/plugins/font_editor_plugin.h +++ b/editor/plugins/editor_resource_conversion_plugin.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* font_editor_plugin.h */ +/* editor_resource_conversion_plugin.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,50 +28,27 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef FONT_EDITOR_PLUGIN_H -#define FONT_EDITOR_PLUGIN_H +#ifndef EDITOR_RESOURCE_CONVERSION_PLUGIN_H +#define EDITOR_RESOURCE_CONVERSION_PLUGIN_H -#include "editor/editor_plugin.h" -#include "scene/resources/font.h" -#include "scene/resources/text_line.h" +#include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" -class FontDataPreview : public Control { - GDCLASS(FontDataPreview, Control); +class EditorResourceConversionPlugin : public RefCounted { + GDCLASS(EditorResourceConversionPlugin, RefCounted); protected: - void _notification(int p_what); static void _bind_methods(); - Ref<TextLine> line; + GDVIRTUAL0RC(String, _converts_to) + GDVIRTUAL1RC(bool, _handles, Ref<Resource>) + GDVIRTUAL1RC(Ref<Resource>, _convert, Ref<Resource>) 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(); - - virtual String get_name() const override { return "Font"; } + 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 // FONT_EDITOR_PLUGIN_H +#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..c7d3e92802 --- /dev/null +++ b/editor/plugins/font_config_plugin.cpp @@ -0,0 +1,1053 @@ +/*************************************************************************/ +/* font_config_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_config_plugin.h" + +#include "editor/editor_scale.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 (Object::cast_to<Button>(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_delete(); + } + 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(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) { + button_add = EditorInspector::create_inspector_action_button(TTR("Add Script")); + } 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_delete(); + } + } + + 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, 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(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 (Object::cast_to<Button>(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(); + } + + 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_delete(); + } + 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); + 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(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 uint32_t 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; + update(); +} + +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 uint32_t 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 uint32_t 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..41dde3cc59 --- /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) 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_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 uint32_t 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 uint32_t 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 uint32_t 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 b9de621bcb..0000000000 --- a/editor/plugins/font_editor_plugin.cpp +++ /dev/null @@ -1,106 +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) { - switch (p_what) { - case 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); - } break; - } -} - -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() { - Ref<EditorInspectorPluginFont> fd_plugin; - fd_plugin.instantiate(); - EditorInspector::add_inspector_plugin(fd_plugin); -} diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index 8ed72b1c42..b5eca46ad3 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -31,14 +31,14 @@ #ifndef GDEXTENSION_EXPORT_PLUGIN_H #define GDEXTENSION_EXPORT_PLUGIN_H -#include "editor/editor_export.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 Set<String> &p_features); + virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features); }; -void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p_type, const Set<String> &p_features) { +void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { if (p_type != "NativeExtension") { return; } @@ -47,14 +47,9 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p config.instantiate(); Error err = config->load(p_path); + ERR_FAIL_COND_MSG(err, "Failed to load GDExtension file: " + p_path); - if (err != OK) { - return; - } - - if (!config->has_section_key("configuration", "entry_symbol")) { - return; - } + 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"); @@ -62,6 +57,7 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p config->get_section_keys("libraries", &libraries); + bool could_export = false; for (const String &E : libraries) { Vector<String> tags = E.split("."); bool all_tags_met = true; @@ -101,13 +97,23 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p String linker_flags = "-Wl,-U,_" + entry_symbol; add_ios_linker_flags(linker_flags); } + could_export = true; break; } } + if (!could_export) { + Vector<String> tags; + for (const String &E : p_features) { + tags.append(E); + } + ERR_FAIL_MSG(vformat("Couldn't export extension: %s. No suitable library found for export flags: %s", p_path, String(", ").join(tags))); + } List<String> dependencies; + if (config->has_section("dependencies")) { + config->get_section_keys("dependencies", &dependencies); + } - config->get_section_keys("dependencies", &dependencies); for (const String &E : libraries) { Vector<String> tags = E.split("."); bool all_tags_met = true; diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp index 72caa15e9c..1487f8b7bc 100644 --- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_2d_editor_plugin.cpp @@ -34,6 +34,7 @@ #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/separator.h" @@ -111,7 +112,7 @@ 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_singleton()->get_undo_redo(); ur->create_action(TTR("Convert to CPUParticles2D")); ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", particles, cpu_particles, true, false); ur->add_do_reference(cpu_particles); @@ -299,12 +300,7 @@ 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); + pm->set_emission_point_texture(ImageTexture::create_from_image(img)); pm->set_emission_point_count(vpc); if (capture_colors) { @@ -320,10 +316,7 @@ 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); + pm->set_emission_color_texture(ImageTexture::create_from_image(img)); } if (valid_normals.size()) { @@ -343,10 +336,7 @@ 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); + pm->set_emission_normal_texture(ImageTexture::create_from_image(img)); } else { pm->set_emission_shape(ParticlesMaterial::EMISSION_SHAPE_POINTS); @@ -391,7 +381,7 @@ GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() { 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 75f68617d1..0229b57c10 100644 --- a/editor/plugins/gpu_particles_2d_editor_plugin.h +++ b/editor/plugins/gpu_particles_2d_editor_plugin.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef PARTICLES_2D_EDITOR_PLUGIN_H -#define PARTICLES_2D_EDITOR_PLUGIN_H +#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" @@ -38,6 +38,7 @@ #include "scene/gui/spin_box.h" class EditorFileDialog; +class EditorUndoRedoManager; class GPUParticles2DEditorPlugin : public EditorPlugin { GDCLASS(GPUParticles2DEditorPlugin, EditorPlugin); @@ -75,7 +76,7 @@ class GPUParticles2DEditorPlugin : public EditorPlugin { String source_emission_file; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; void _file_selected(const String &p_file); void _menu_callback(int p_idx); void _generate_visibility_rect(); @@ -97,4 +98,4 @@ public: ~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 4b1081ed92..335efd6949 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp @@ -32,6 +32,7 @@ #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" @@ -42,7 +43,7 @@ bool GPUParticles3DEditorBase::_generate(Vector<Vector3> &points, Vector<Vector3 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(); @@ -63,9 +64,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 @@ -213,9 +214,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); @@ -271,7 +272,7 @@ 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_singleton()->get_undo_redo(); ur->create_action(TTR("Convert to CPUParticles3D")); ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", node, cpu_particles, true, false); ur->add_do_reference(cpu_particles); @@ -321,7 +322,7 @@ void GPUParticles3DEditor::_generate_aabb() { node->set_emitting(false); } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_singleton()->get_undo_redo(); 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()); @@ -363,10 +364,7 @@ void GPUParticles3DEditor::_generate_emission_points() { } Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img)); - - Ref<ImageTexture> tex; - tex.instantiate(); - tex->create_from_image(image); + Ref<ImageTexture> tex = ImageTexture::create_from_image(image); Ref<ParticlesMaterial> material = node->get_process_material(); ERR_FAIL_COND(material.is_null()); @@ -392,12 +390,7 @@ 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); + material->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); diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.h b/editor/plugins/gpu_particles_3d_editor_plugin.h index 6ba6d102ef..17bdfa6e3f 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.h +++ b/editor/plugins/gpu_particles_3d_editor_plugin.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef PARTICLES_EDITOR_PLUGIN_H -#define PARTICLES_EDITOR_PLUGIN_H +#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" @@ -114,4 +114,4 @@ public: ~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 643a470425..b54cb515e4 100644 --- a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp @@ -140,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; } diff --git a/editor/plugins/gradient_editor_plugin.cpp b/editor/plugins/gradient_editor_plugin.cpp index 1386f03662..460178490e 100644 --- a/editor/plugins/gradient_editor_plugin.cpp +++ b/editor/plugins/gradient_editor_plugin.cpp @@ -33,6 +33,8 @@ #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 { @@ -54,7 +56,7 @@ void GradientEditor::_gradient_changed() { void GradientEditor::_ramp_changed() { editing = true; - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_undo_redo(); 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()); @@ -85,6 +87,7 @@ void GradientEditor::reverse_gradient() { } GradientEditor::GradientEditor() { + GradientEdit::get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(GradientEdit::get_picker())); editing = false; } diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.cpp b/editor/plugins/gradient_texture_2d_editor_plugin.cpp index e97c611e96..6c463f71cf 100644 --- a/editor/plugins/gradient_texture_2d_editor_plugin.cpp +++ b/editor/plugins/gradient_texture_2d_editor_plugin.cpp @@ -32,6 +32,7 @@ #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" @@ -117,17 +118,15 @@ void GradientTexture2DEditorRect::_notification(int p_what) { const Ref<Texture2D> fill_to_icon = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons")); handle_size = fill_from_icon->get_size(); - const int MAX_HEIGHT = 250 * EDSCALE; Size2 rect_size = get_size(); // Get the size and position to draw the texture and handles at. - size = Size2(texture->get_width() * MAX_HEIGHT / texture->get_height(), MAX_HEIGHT); + size = Size2(texture->get_width() * rect_size.height / texture->get_height(), rect_size.height); if (size.width > rect_size.width) { size.width = rect_size.width; - size.height = texture->get_height() * rect_size.width / texture->get_width(); + size.height = texture->get_height() * size.width / texture->get_width(); } - offset = Point2(Math::round((rect_size.width - size.width) / 2), 0) + handle_size / 2; - set_custom_minimum_size(Size2(0, size.height)); + offset = ((rect_size - size + handle_size) / 2).round(); size -= handle_size; checkerboard->set_rect(Rect2(offset, size)); @@ -177,12 +176,14 @@ void GradientTexture2DEditorRect::_notification(int p_what) { } GradientTexture2DEditorRect::GradientTexture2DEditorRect() { - undo_redo = EditorNode::get_singleton()->get_undo_redo(); + undo_redo = EditorNode::get_undo_redo(); checkerboard = memnew(TextureRect); checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE); checkerboard->set_draw_behind_parent(true); add_child(checkerboard); + + set_custom_minimum_size(Size2(0, 250 * EDSCALE)); } /////////////////////// @@ -222,7 +223,7 @@ void GradientTexture2DEditor::_notification(int p_what) { } GradientTexture2DEditor::GradientTexture2DEditor() { - undo_redo = EditorNode::get_singleton()->get_undo_redo(); + undo_redo = EditorNode::get_undo_redo(); HFlowContainer *toolbar = memnew(HFlowContainer); add_child(toolbar); diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.h b/editor/plugins/gradient_texture_2d_editor_plugin.h index 4ce64ce1dc..9faf33152a 100644 --- a/editor/plugins/gradient_texture_2d_editor_plugin.h +++ b/editor/plugins/gradient_texture_2d_editor_plugin.h @@ -28,12 +28,14 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GRADIENT_TEXTURE_2D_EDITOR -#define GRADIENT_TEXTURE_2D_EDITOR +#ifndef GRADIENT_TEXTURE_2D_EDITOR_PLUGIN_H +#define GRADIENT_TEXTURE_2D_EDITOR_PLUGIN_H #include "editor/editor_plugin.h" #include "editor/editor_spin_slider.h" +class EditorUndoRedoManager; + class GradientTexture2DEditorRect : public Control { GDCLASS(GradientTexture2DEditorRect, Control); @@ -44,7 +46,7 @@ class GradientTexture2DEditorRect : public Control { }; Ref<GradientTexture2D> texture; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; bool snap_enabled = false; float snap_size = 0; @@ -74,7 +76,7 @@ class GradientTexture2DEditor : public VBoxContainer { GDCLASS(GradientTexture2DEditor, VBoxContainer); Ref<GradientTexture2D> texture; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; Button *reverse_button = nullptr; Button *snap_button = nullptr; @@ -109,4 +111,4 @@ public: GradientTexture2DEditorPlugin(); }; -#endif +#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 fb0e260388..153eab32d2 100644 --- a/editor/plugins/input_event_editor_plugin.cpp +++ b/editor/plugins/input_event_editor_plugin.cpp @@ -80,12 +80,11 @@ void InputEventConfigContainer::set_event(const Ref<InputEvent> &p_event) { InputEventConfigContainer::InputEventConfigContainer() { input_event_text = memnew(Label); input_event_text->set_h_size_flags(SIZE_EXPAND_FILL); - input_event_text->set_autowrap_mode(Label::AutowrapMode::AUTOWRAP_WORD_SMART); + 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 = memnew(Button); - open_config_button->set_text(TTR("Configure")); + open_config_button = EditorInspector::create_inspector_action_button(TTR("Configure")); open_config_button->connect("pressed", callable_mp(this, &InputEventConfigContainer::_configure_pressed)); add_child(open_config_button); diff --git a/editor/plugins/light_occluder_2d_editor_plugin.cpp b/editor/plugins/light_occluder_2d_editor_plugin.cpp index 4fbc8188b9..e7ef65c32b 100644 --- a/editor/plugins/light_occluder_2d_editor_plugin.cpp +++ b/editor/plugins/light_occluder_2d_editor_plugin.cpp @@ -96,7 +96,7 @@ void LightOccluder2DEditor::_create_resource() { 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); diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index aef97f059a..8413c5e875 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -138,7 +138,7 @@ LightmapGIEditorPlugin::LightmapGIEditorPlugin() { file_dialog = memnew(EditorFileDialog); file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); - file_dialog->add_filter("*.lmbake ; " + TTR("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 1202efe8fc..a06f97fc94 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.h +++ b/editor/plugins/lightmap_gi_editor_plugin.h @@ -28,8 +28,8 @@ /* 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_plugin.h" #include "scene/3d/lightmap_gi.h" @@ -67,4 +67,4 @@ public: ~LightmapGIEditorPlugin(); }; -#endif +#endif // LIGHTMAP_GI_EDITOR_PLUGIN_H diff --git a/editor/plugins/material_editor_plugin.cpp b/editor/plugins/material_editor_plugin.cpp index eb004568d0..5d59f62f05 100644 --- a/editor/plugins/material_editor_plugin.cpp +++ b/editor/plugins/material_editor_plugin.cpp @@ -32,6 +32,8 @@ #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" @@ -130,7 +132,7 @@ MaterialEditor::MaterialEditor() { 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); @@ -143,7 +145,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(); @@ -155,7 +157,9 @@ MaterialEditor::MaterialEditor() { camera = memnew(Camera3D); camera->set_transform(Transform3D(Basis(), Vector3(0, 0, 3))); - camera->set_perspective(45, 0.1, 10); + // 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(); viewport->add_child(camera); @@ -177,8 +181,8 @@ MaterialEditor::MaterialEditor() { 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_xform.basis.scale(Vector3(0.7, 0.7, 0.7)); + box_xform.origin.y = 0.05; box_instance->set_transform(box_xform); sphere_mesh.instantiate(); @@ -190,7 +194,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); @@ -199,13 +203,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(); @@ -215,12 +219,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)); + light_2_switch->connect("pressed", callable_mp(this, &MaterialEditor::_button_pressed).bind(light_2_switch)); first_enter = true; @@ -258,10 +262,8 @@ void EditorInspectorPluginMaterial::parse_begin(Object *p_object) { } void EditorInspectorPluginMaterial::_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); - if (!undo_redo) { - return; - } + Ref<EditorUndoRedoManager> undo_redo = Object::cast_to<EditorUndoRedoManager>(p_undo_redo); + ERR_FAIL_COND(!undo_redo.is_valid()); // 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 @@ -337,17 +339,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()->shader_get_shader_uniform_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_uniform(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_uniform(E.name, value); } } @@ -383,17 +385,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()->shader_get_shader_uniform_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_uniform(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_uniform(E.name, value); } } @@ -429,11 +431,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()->shader_get_shader_uniform_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_uniform(E.name, value); } smat->set_render_priority(mat->get_render_priority()); @@ -468,11 +470,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()->shader_get_shader_uniform_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_uniform(E.name, value); } smat->set_render_priority(mat->get_render_priority()); @@ -507,11 +509,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()->shader_get_shader_uniform_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_uniform(E.name, value); } smat->set_render_priority(mat->get_render_priority()); @@ -546,11 +548,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()->shader_get_shader_uniform_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_uniform(E.name, value); } smat->set_render_priority(mat->get_render_priority()); @@ -585,11 +587,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()->shader_get_shader_uniform_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_uniform(E.name, value); } smat->set_render_priority(mat->get_render_priority()); @@ -624,11 +626,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()->shader_get_shader_uniform_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_uniform(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 9c6247d59b..fc3da5fd9f 100644 --- a/editor/plugins/material_editor_plugin.h +++ b/editor/plugins/material_editor_plugin.h @@ -32,7 +32,7 @@ #define MATERIAL_EDITOR_PLUGIN_H #include "editor/editor_plugin.h" -#include "editor/property_editor.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" diff --git a/editor/plugins/mesh_editor_plugin.cpp b/editor/plugins/mesh_editor_plugin.cpp index 4760b61dc8..31c9f1e387 100644 --- a/editor/plugins/mesh_editor_plugin.cpp +++ b/editor/plugins/mesh_editor_plugin.cpp @@ -112,7 +112,7 @@ 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(Viewport::MSAA_4X); set_stretch(true); camera = memnew(Camera3D); camera->set_transform(Transform3D(Basis(), Vector3(0, 0, 1.1))); @@ -137,7 +137,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(); @@ -147,12 +147,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)); + light_2_switch->connect("pressed", callable_mp(this, &MeshEditor::_button_pressed).bind(light_2_switch)); first_enter = true; diff --git a/editor/plugins/mesh_editor_plugin.h b/editor/plugins/mesh_editor_plugin.h index 3554b3c1e9..fb61f03485 100644 --- a/editor/plugins/mesh_editor_plugin.h +++ b/editor/plugins/mesh_editor_plugin.h @@ -87,4 +87,4 @@ public: 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 d33803213a..7bd406b869 100644 --- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp +++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp @@ -32,6 +32,7 @@ #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" @@ -60,7 +61,7 @@ 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_singleton()->get_undo_redo(); List<Node *> selection = editor_selection->get_selected_node_list(); @@ -77,12 +78,14 @@ void MeshInstance3DEditor::_menu_option(int p_option) { StaticBody3D *body = memnew(StaticBody3D); 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(), "_request_gizmo", body); + ur->add_do_method(Node3DEditor::get_singleton(), "_request_gizmo", cshape); ur->add_do_reference(body); ur->add_undo_method(node, "remove_child", body); ur->commit_action(); @@ -112,11 +115,13 @@ void MeshInstance3DEditor::_menu_option(int p_option) { StaticBody3D *body = memnew(StaticBody3D); 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(), "_request_gizmo", body); + ur->add_do_method(Node3DEditor::get_singleton(), "_request_gizmo", cshape); ur->add_do_reference(body); ur->add_undo_method(instance, "remove_child", body); } @@ -141,15 +146,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_singleton()->get_undo_redo(); 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(), "_request_gizmo", cshape); ur->add_do_reference(cshape); ur->add_undo_method(node->get_parent(), "remove_child", cshape); ur->commit_action(); @@ -172,7 +178,7 @@ void MeshInstance3DEditor::_menu_option(int p_option) { err_dialog->popup_centered(); return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_singleton()->get_undo_redo(); if (simplify) { ur->create_action(TTR("Create Simplified Convex Shape")); @@ -184,11 +190,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(), "_request_gizmo", cshape); ur->add_do_reference(cshape); ur->add_undo_method(node->get_parent(), "remove_child", cshape); @@ -211,20 +218,23 @@ void MeshInstance3DEditor::_menu_option(int p_option) { err_dialog->popup_centered(); return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_singleton()->get_undo_redo(); 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(), "_request_gizmo", cshape); ur->add_do_reference(cshape); ur->add_undo_method(node->get_parent(), "remove_child", cshape); } @@ -243,13 +253,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_singleton()->get_undo_redo(); 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(), "_request_gizmo", nmi); ur->add_do_reference(nmi); ur->add_undo_method(node, "remove_child", nmi); @@ -267,13 +278,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; } + Ref<EditorUndoRedoManager> &ur = EditorNode::get_singleton()->get_undo_redo(); + 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(); @@ -300,12 +350,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() {} @@ -324,7 +375,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) { @@ -418,17 +469,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_singleton()->get_undo_redo(); 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(), "_request_gizmo", mi); ur->add_do_reference(mi); ur->add_undo_method(node, "remove_child", mi); @@ -471,7 +520,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); diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.h b/editor/plugins/mesh_instance_3d_editor_plugin.h index 36d8eacd98..7968176744 100644 --- a/editor/plugins/mesh_instance_3d_editor_plugin.h +++ b/editor/plugins/mesh_instance_3d_editor_plugin.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef MESH_INSTANCE_EDITOR_PLUGIN_H -#define MESH_INSTANCE_EDITOR_PLUGIN_H +#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" @@ -97,4 +97,4 @@ public: ~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 423ec5f4ed..319f6ee9de 100644 --- a/editor/plugins/mesh_library_editor_plugin.cpp +++ b/editor/plugins/mesh_library_editor_plugin.cpp @@ -71,7 +71,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); @@ -263,7 +263,7 @@ MeshLibraryEditor::MeshLibraryEditor() { 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)); @@ -288,9 +288,9 @@ MeshLibraryEditor::MeshLibraryEditor() { 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(TTR("Apply without Transforms")); - cd_update->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(false)); - cd_update->add_button(TTR("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) { diff --git a/editor/plugins/mesh_library_editor_plugin.h b/editor/plugins/mesh_library_editor_plugin.h index 85ead35534..f4b4288a5f 100644 --- a/editor/plugins/mesh_library_editor_plugin.h +++ b/editor/plugins/mesh_library_editor_plugin.h @@ -47,8 +47,8 @@ class MeshLibraryEditor : public Control { ConfirmationDialog *cd_remove = nullptr; ConfirmationDialog *cd_update = nullptr; EditorFileDialog *file = nullptr; - bool apply_xforms; - int to_erase; + bool apply_xforms = false; + int to_erase = 0; enum { MENU_OPTION_ADD_ITEM, @@ -58,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(); diff --git a/editor/plugins/multimesh_editor_plugin.cpp b/editor/plugins/multimesh_editor_plugin.cpp index 850c673c12..fc4dc5bc2f 100644 --- a/editor/plugins/multimesh_editor_plugin.cpp +++ b/editor/plugins/multimesh_editor_plugin.cpp @@ -141,7 +141,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) { @@ -180,9 +180,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 @@ -200,9 +200,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(); @@ -291,7 +291,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); @@ -303,7 +303,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")); @@ -347,7 +347,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); diff --git a/editor/plugins/multimesh_editor_plugin.h b/editor/plugins/multimesh_editor_plugin.h index 9f5e85216c..5773989d0d 100644 --- a/editor/plugins/multimesh_editor_plugin.h +++ b/editor/plugins/multimesh_editor_plugin.h @@ -46,7 +46,7 @@ class MultiMeshEditor : public Control { AcceptDialog *err_dialog = nullptr; MenuButton *options = nullptr; MultiMeshInstance3D *_last_pp_node = nullptr; - bool browsing_source; + bool browsing_source = false; Panel *panel = nullptr; MultiMeshInstance3D *node = nullptr; diff --git a/editor/plugins/navigation_polygon_editor_plugin.cpp b/editor/plugins/navigation_polygon_editor_plugin.cpp index 17eaf45bb5..8f3553b8cf 100644 --- a/editor/plugins/navigation_polygon_editor_plugin.cpp +++ b/editor/plugins/navigation_polygon_editor_plugin.cpp @@ -106,7 +106,7 @@ void NavigationPolygonEditor::_create_resource() { 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); diff --git a/editor/plugins/navigation_polygon_editor_plugin.h b/editor/plugins/navigation_polygon_editor_plugin.h index 7550b75fa3..239da88ba2 100644 --- a/editor/plugins/navigation_polygon_editor_plugin.h +++ b/editor/plugins/navigation_polygon_editor_plugin.h @@ -28,8 +28,8 @@ /* 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" @@ -67,4 +67,4 @@ public: 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 47c8c66c57..043848080f 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -35,6 +35,7 @@ #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" @@ -51,12 +52,13 @@ #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_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/shape_cast_3d.h" #include "scene/3d/soft_dynamic_body_3d.h" #include "scene/3d/spring_arm_3d.h" #include "scene/3d/sprite_3d.h" @@ -74,6 +76,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 @@ -245,6 +248,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) { @@ -589,7 +593,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; @@ -665,7 +669,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); } @@ -689,13 +693,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; @@ -742,7 +746,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(); @@ -1319,7 +1323,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)) { @@ -1344,13 +1348,13 @@ void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_i 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -1497,6 +1501,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"); } @@ -1565,7 +1572,7 @@ void AudioStreamPlayer3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gi player->set_emission_angle(p_restore); } else { - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -1580,6 +1587,88 @@ void AudioStreamPlayer3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { 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::deg2rad((float)(i * 3)); + const float rb = Math::deg2rad((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)); @@ -1726,7 +1815,7 @@ void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_ if (p_cancel) { camera->set("fov", p_restore); } else { - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Change Camera FOV")); ur->add_do_property(camera, "fov", camera->get_fov()); ur->add_undo_property(camera, "fov", p_restore); @@ -1737,7 +1826,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Change Camera Size")); ur->add_do_property(camera, "size", camera->get_size()); ur->add_undo_property(camera, "size", p_restore); @@ -2053,7 +2142,7 @@ void OccluderInstance3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_giz return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -2067,7 +2156,7 @@ void OccluderInstance3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_giz return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -2081,7 +2170,7 @@ void OccluderInstance3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_giz return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -2203,7 +2292,7 @@ void Label3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { /// -Position3DGizmoPlugin::Position3DGizmoPlugin() { +Marker3DGizmoPlugin::Marker3DGizmoPlugin() { pos3d_mesh = Ref<ArrayMesh>(memnew(ArrayMesh)); cursor_points = Vector<Vector3>(); @@ -2227,7 +2316,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); @@ -2263,19 +2352,19 @@ 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) { p_gizmo->clear(); p_gizmo->add_mesh(pos3d_mesh); p_gizmo->add_collision_segments(cursor_points); @@ -2454,6 +2543,44 @@ void RayCast3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ///// +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_spatial_node()); + + p_gizmo->clear(); + + 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); + + 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_spatial_node()); @@ -2744,7 +2871,7 @@ void VisibleOnScreenNotifier3DGizmoPlugin::commit_handle(const EditorNode3DGizmo return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -2935,7 +3062,7 @@ void GPUParticles3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -2993,10 +3120,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"); } @@ -3096,7 +3228,7 @@ void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo * return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -3109,7 +3241,7 @@ void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo * return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::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); @@ -3122,12 +3254,17 @@ void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { 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"); @@ -3363,7 +3500,7 @@ void ReflectionProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Change Probe Extents")); ur->add_do_method(probe, "set_extents", probe->get_extents()); ur->add_do_method(probe, "set_origin_offset", probe->get_origin_offset()); @@ -3515,7 +3652,7 @@ void DecalGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::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); @@ -3655,7 +3792,7 @@ void VoxelGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_i return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::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); @@ -3789,7 +3926,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) { @@ -4270,7 +4407,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -4284,7 +4421,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -4301,7 +4438,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); if (p_id == 0) { ur->create_action(TTR("Change Capsule Shape Radius")); ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius()); @@ -4326,7 +4463,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); if (p_id == 0) { ur->create_action(TTR("Change Cylinder Shape Radius")); ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius()); @@ -4351,7 +4488,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -4703,10 +4840,10 @@ 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()); } bool NavigationRegion3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { @@ -4722,24 +4859,19 @@ 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_spatial_node()); 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(); + 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; @@ -4755,7 +4887,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); @@ -4773,10 +4905,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; @@ -4796,18 +4928,74 @@ 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)); + } } ////// @@ -4832,7 +5020,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; @@ -4857,7 +5045,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; @@ -4876,7 +5064,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; @@ -4888,7 +5076,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; @@ -4907,7 +5095,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; @@ -4919,7 +5107,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; @@ -4938,7 +5126,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; @@ -4948,8 +5136,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) { @@ -4991,20 +5179,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); } } @@ -5020,17 +5208,17 @@ void JointGizmosDrawer::draw_cone(const Transform3D &p_offset, const Basis &p_ba 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); @@ -5044,8 +5232,8 @@ void JointGizmosDrawer::draw_cone(const Transform3D &p_offset, const Basis &p_ba 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); } } @@ -5229,17 +5417,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; @@ -5272,34 +5460,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) { @@ -5422,13 +5610,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) { @@ -5558,7 +5746,7 @@ void FogVolumeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::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); diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index f859ceda3b..7dac1bd360 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -31,8 +31,8 @@ #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" @@ -334,8 +334,8 @@ public: Label3DGizmoPlugin(); }; -class Position3DGizmoPlugin : public EditorNode3DGizmoPlugin { - GDCLASS(Position3DGizmoPlugin, EditorNode3DGizmoPlugin); +class Marker3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(Marker3DGizmoPlugin, EditorNode3DGizmoPlugin); Ref<ArrayMesh> pos3d_mesh; Vector<Vector3> cursor_points; @@ -346,7 +346,7 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - Position3DGizmoPlugin(); + Marker3DGizmoPlugin(); }; class PhysicalBone3DGizmoPlugin : public EditorNode3DGizmoPlugin { @@ -373,6 +373,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); @@ -601,7 +613,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: diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 7e01593bda..6add9e2e1e 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -33,8 +33,8 @@ #include "core/config/project_settings.h" #include "core/input/input.h" #include "core/input/input_map.h" -#include "core/math/camera_matrix.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" @@ -46,12 +46,14 @@ #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/flow_container.h" #include "scene/gui/subviewport_container.h" #include "scene/resources/packed_scene.h" #include "scene/resources/surface_tool.h" @@ -145,7 +147,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(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)); + 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); @@ -159,7 +161,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) { @@ -355,21 +357,21 @@ void Node3DEditorViewport::_update_camera(real_t p_interp_delta) { 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 { - const Map<Node *, Object *> &selection = editor_selection->get_selection(); + const HashMap<Node *, Object *> &selection = editor_selection->get_selection(); int count = 0; @@ -411,6 +413,12 @@ void Node3DEditorViewport::cancel_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); } @@ -440,7 +448,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 { @@ -508,7 +516,7 @@ void Node3DEditorViewport::_select_clicked(bool p_allow_locked) { } } -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(); @@ -518,7 +526,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; @@ -581,7 +589,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])); @@ -635,7 +643,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 { @@ -644,10 +652,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))); } @@ -764,7 +772,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(); @@ -983,7 +991,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).normalized() * 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; @@ -1003,15 +1011,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); @@ -1064,7 +1072,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b float col_d = 1e20; for (int i = 0; i < 3; i++) { - Plane plane(gt.basis.get_axis(i).normalized(), gt.origin); + Plane plane(gt.basis.get_column(i).normalized(), gt.origin); Vector3 r; if (!plane.intersects_ray(ray_pos, ray, &r)) { continue; @@ -1103,7 +1111,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).normalized() * 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; @@ -1123,15 +1131,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); @@ -1253,7 +1261,9 @@ void Node3DEditorViewport::_surface_mouse_enter() { } void Node3DEditorViewport::_surface_mouse_exit() { - _remove_preview(); + _remove_preview_node(); + _reset_preview_material(); + _remove_preview_material(); } void Node3DEditorViewport::_surface_focus_enter() { @@ -1265,7 +1275,7 @@ 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) { @@ -1377,25 +1387,17 @@ 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: { @@ -1888,7 +1890,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (EditorSettings::get_singleton()->get("editors/3d/navigation/emulate_numpad")) { - const Key code = k->get_keycode(); + 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); } @@ -2093,7 +2095,7 @@ void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const 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"); @@ -2103,7 +2105,7 @@ void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const (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; } @@ -2373,25 +2375,21 @@ 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 = ProjectSettings::get_singleton()->get("rendering/shadows/positional_shadow/atlas_size"); + bool shadowmap_16_bits = ProjectSettings::get_singleton()->get("rendering/shadows/positional_shadow/atlas_16_bits"); + int atlas_q0 = ProjectSettings::get_singleton()->get("rendering/shadows/positional_shadow/atlas_quadrant_0_subdiv"); + int atlas_q1 = ProjectSettings::get_singleton()->get("rendering/shadows/positional_shadow/atlas_quadrant_1_subdiv"); + int atlas_q2 = ProjectSettings::get_singleton()->get("rendering/shadows/positional_shadow/atlas_quadrant_2_subdiv"); + int atlas_q3 = ProjectSettings::get_singleton()->get("rendering/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 @@ -2399,6 +2397,9 @@ void Node3DEditorViewport::_project_settings_changed() { viewport->set_msaa(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 use_debanding = GLOBAL_GET("rendering/anti_aliasing/quality/use_debanding"); viewport->set_use_debanding(use_debanding); @@ -2407,6 +2408,18 @@ 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) { @@ -2465,7 +2478,7 @@ void Node3DEditorViewport::_notification(int p_what) { _update_camera(delta); - const Map<Node *, Object *> &selection = editor_selection->get_selection(); + const HashMap<Node *, Object *> &selection = editor_selection->get_selection(); bool changed = false; bool exist = false; @@ -2502,14 +2515,14 @@ void Node3DEditorViewport::_notification(int p_what) { 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.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(se->aabb.position - offset / 2); + t_offset.translate_local(se->aabb.position - offset / 2); t_offset.basis = t_offset.basis * aabb_s; } @@ -2685,6 +2698,13 @@ void Node3DEditorViewport::_notification(int p_what) { 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; + + 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; } } @@ -2708,7 +2728,8 @@ static void draw_indicator_bar(Control &p_surface, real_t p_fill, const Ref<Text p_surface.draw_texture(p_icon, icon_pos, p_color); // Draw text below the bar (for speed/zoom information). - 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, Math::round(2 * EDSCALE), Color(0, 0, 0)); + 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() { @@ -2952,6 +2973,13 @@ void Node3DEditorViewport::_menu_option(int p_option) { 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()); } @@ -2979,7 +3007,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(); @@ -2990,7 +3027,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()); } @@ -3085,8 +3122,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); @@ -3124,7 +3161,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, @@ -3152,6 +3190,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[] = { @@ -3181,6 +3220,7 @@ 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; @@ -3231,6 +3271,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()); @@ -3239,6 +3280,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()); @@ -3247,6 +3289,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()); @@ -3255,6 +3298,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()); @@ -3263,6 +3307,7 @@ 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()); @@ -3270,6 +3315,8 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { 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 @@ -3280,6 +3327,7 @@ 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() { @@ -3389,8 +3437,8 @@ void Node3DEditorViewport::update_transform_gizmo_view() { 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; @@ -3424,8 +3472,8 @@ void Node3DEditorViewport::update_transform_gizmo_view() { for (int i = 0; i < 3; i++) { Transform3D axis_angle = Transform3D(); - if (xform.basis.get_axis(i).normalized().dot(xform.basis.get_axis((i + 1) % 3).normalized()) < 1.0) { - axis_angle = axis_angle.looking_at(xform.basis.get_axis(i).normalized(), xform.basis.get_axis((i + 1) % 3).normalized()); + 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; @@ -3764,10 +3812,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)); @@ -3791,7 +3839,7 @@ void Node3DEditorViewport::_create_preview(const Vector<String> &files) const { *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); @@ -3802,6 +3850,106 @@ void Node3DEditorViewport::_remove_preview() { } } +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 = 0; surface < surface_count; surface++) { + Ref<TriangleMesh> surface_mesh = mesh->generate_surface_triangle_mesh(surface); + + 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; + 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) { if (p_desired_node->get_scene_file_path() == p_target_scene_path) { return true; @@ -3818,7 +3966,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)); @@ -3872,15 +4020,15 @@ 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, true); - editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", EditorNode::get_singleton()->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); + editor_data->get_undo_redo()->add_do_method(parent, "add_child", instantiated_scene, true); + editor_data->get_undo_redo()->add_do_method(instantiated_scene, "set_owner", EditorNode::get_singleton()->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); 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", EditorNode::get_singleton()->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(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + editor_data->get_undo_redo()->add_do_method(ed, "live_debug_instance_node", EditorNode::get_singleton()->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(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); Node3D *node3d = Object::cast_to<Node3D>(instantiated_scene); if (node3d) { @@ -3893,22 +4041,41 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po global_transform.origin = spatial_editor->snap_point(_get_instance_position(p_point)); global_transform.basis *= node3d->get_transform().basis; - editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_global_transform", global_transform); + editor_data->get_undo_redo()->add_do_method(instantiated_scene, "set_global_transform", global_transform); } return true; } void Node3DEditorViewport::_perform_drop_data() { - _remove_preview(); + 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) { + editor_data->get_undo_redo()->create_action(vformat(TTR("Set Surface %d Override Material"), spatial_editor->get_preview_material_surface())); + editor_data->get_undo_redo()->add_do_method(geometry_instance, "set_surface_override_material", spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_material()); + editor_data->get_undo_redo()->add_undo_method(geometry_instance, "set_surface_override_material", spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_reset_material()); + editor_data->get_undo_redo()->commit_action(); + } else if (geometry_instance) { + editor_data->get_undo_redo()->create_action(TTR("Set Material Override")); + editor_data->get_undo_redo()->add_do_method(geometry_instance, "set_material_override", spatial_editor->get_preview_material()); + editor_data->get_undo_redo()->add_undo_method(geometry_instance, "set_material_override", spatial_editor->get_preview_reset_material()); + editor_data->get_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")); + editor_data->get_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; } @@ -3922,7 +4089,7 @@ void Node3DEditorViewport::_perform_drop_data() { } } - editor_data->get_undo_redo().commit_action(); + editor_data->get_undo_redo()->commit_action(); if (error_files.size() > 0) { String files_str; @@ -3938,7 +4105,7 @@ void Node3DEditorViewport::_perform_drop_data() { bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { 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"]; @@ -3947,40 +4114,81 @@ 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++) { // Check if dragged files with mesh or scene extension can be created at least once. - if (mesh_extensions.find(files[i].get_extension()) || scene_extensions.find(files[i].get_extension())) { - RES res = ResourceLoader::load(files[i]); + if (mesh_extensions.find(files[i].get_extension()) || + scene_extensions.find(files[i].get_extension()) || + material_extensions.find(files[i].get_extension()) || + texture_extensions.find(files[i].get_extension())) { + Ref<Resource> res = ResourceLoader::load(files[i]); if (res.is_null()) { continue; } 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 (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; } can_instantiate = true; break; } } if (can_instantiate) { - _create_preview(files); + _create_preview_node(files); } } } 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); + return true; + } + + 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 can_instantiate; + return false; } void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { @@ -4018,7 +4226,7 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_ } else { accept->set_text(TTR("Cannot drag and drop into multiple selected nodes.")); accept->popup_centered(); - _remove_preview(); + _remove_preview_node(); return; } @@ -4087,30 +4295,30 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) { plane = Plane(_get_camera_normal(), _edit.center); break; case TRANSFORM_X_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0).normalized(); + 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_axis(1).normalized(); + 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_axis(2).normalized(); + 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_axis(2).normalized() + spatial_editor->get_gizmo_transform().basis.get_axis(1).normalized(); - plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0).normalized(), _edit.center); + 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_axis(2).normalized() + spatial_editor->get_gizmo_transform().basis.get_axis(0).normalized(); - plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1).normalized(), _edit.center); + 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_axis(0).normalized() + spatial_editor->get_gizmo_transform().basis.get_axis(1).normalized(); - plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2).normalized(), _edit.center); + 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; } @@ -4159,7 +4367,8 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) { 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) + ", " + + // 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) + ")"); motion = _edit.original.basis.inverse().xform(motion); @@ -4209,27 +4418,27 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) { plane = Plane(_get_camera_normal(), _edit.center); break; case TRANSFORM_X_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0).normalized(); + 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_axis(1).normalized(); + 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_axis(2).normalized(); + 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_axis(0).normalized(), _edit.center); + 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_axis(1).normalized(), _edit.center); + 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_axis(2).normalized(), _edit.center); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(), _edit.center); plane_mv = true; break; } @@ -4259,7 +4468,8 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) { } Vector3 motion_snapped = motion; motion_snapped.snap(Vector3(snap, snap, snap)); - set_message(TTR("Translating: ") + "(" + String::num(motion_snapped.x, snap_step_decimals) + ", " + + // 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) + ")"); motion = spatial_editor->get_gizmo_transform().basis.inverse().xform(motion); @@ -4418,7 +4628,7 @@ void Node3DEditorViewport::register_shortcut_action(const String &p_path, const 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), varray(sc, p_path)); + 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. @@ -4463,7 +4673,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p 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); @@ -4471,7 +4681,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p surface = memnew(Control); surface->set_drag_forwarding(this); 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); @@ -4492,6 +4702,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p 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); @@ -4515,6 +4726,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p 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); @@ -4545,6 +4757,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p display_submenu->add_radio_check_item(TTR("Decal Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_DECALS); 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); @@ -4664,12 +4877,29 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p zoom_limit_label->hide(); surface->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); @@ -5165,7 +5395,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()); @@ -5183,7 +5415,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; } @@ -5970,6 +6204,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); } @@ -6185,7 +6420,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_uniform("albedo", col); rotate_gizmo_color[i] = rotate_mat; Array arrays = surftool->commit_to_arrays(); @@ -6193,7 +6428,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_uniform("albedo", albedo); rotate_gizmo_color_hl[i] = rotate_mat_hl; if (i == 2) { // Rotation white outline @@ -6234,7 +6469,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_uniform("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); @@ -6351,18 +6586,6 @@ void fragment() { _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(); @@ -6469,7 +6692,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)) { @@ -6499,8 +6722,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_uniform("grid_size", grid_fade_size); + grid_mat[c]->set_shader_uniform("orthogonal", orthogonal); // Cache these so we don't have to re-access memory. Vector<Vector3> &ref_grid = grid_points[c]; @@ -6592,6 +6815,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); } } @@ -6610,7 +6834,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 @@ -6685,8 +6909,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); @@ -6694,24 +6918,24 @@ 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; @@ -6728,20 +6952,21 @@ void Node3DEditor::snap_selected_nodes_to_floor() { Vector3 position_offset = Vector3(); // 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; @@ -6754,9 +6979,9 @@ 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()); + AABB aabb = (*vi.begin())->get_transformed_aabb(); + for (const VisualInstance3D *I : vi) { + aabb.merge_with(I->get_transformed_aabb()); } Vector3 size = aabb.size * Vector3(0.5, 0.0, 0.5); from = aabb.position + size; @@ -6798,7 +7023,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { 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; @@ -6820,7 +7045,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { 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; @@ -6949,6 +7174,12 @@ void Node3DEditor::_update_theme() { 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) { @@ -6963,8 +7194,8 @@ void Node3DEditor::_notification(int p_what) { 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)); - EditorNode::get_singleton()->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(false)); - EditorNode::get_singleton()->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(); @@ -6987,7 +7218,6 @@ void Node3DEditor::_notification(int p_what) { 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; @@ -7037,12 +7267,20 @@ Vector<int> Node3DEditor::get_subgizmo_selection() { return ret; } +void Node3DEditor::set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo) { + undo_redo = p_undo_redo; +} + +Ref<EditorUndoRedoManager> Node3DEditor::get_undo_redo() { + return undo_redo; +} + 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) { @@ -7199,7 +7437,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(); } @@ -7277,8 +7515,9 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<SoftDynamicBody3DGizmoPlugin>(memnew(SoftDynamicBody3DGizmoPlugin))); add_gizmo_plugin(Ref<Sprite3DGizmoPlugin>(memnew(Sprite3DGizmoPlugin))); add_gizmo_plugin(Ref<Label3DGizmoPlugin>(memnew(Label3DGizmoPlugin))); - add_gizmo_plugin(Ref<Position3DGizmoPlugin>(memnew(Position3DGizmoPlugin))); + 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))); @@ -7338,11 +7577,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_uniform("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_uniform("sun_color", Vector3(color.r, color.g, color.b)); } void Node3DEditor::_preview_settings_changed() { @@ -7396,7 +7635,7 @@ void Node3DEditor::_load_default_preview_settings() { 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); @@ -7495,163 +7734,155 @@ Node3DEditor::Node3DEditor() { 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)); + 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]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_LIST_SELECT)); tool_button[TOOL_MODE_LIST_SELECT]->set_tooltip(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]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_LOCK_SELECTED)); tool_button[TOOL_LOCK_SELECTED]->set_tooltip(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_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]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNLOCK_SELECTED)); tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip(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_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(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_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(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)); - 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_toggle_mode(true); sun_button->set_flat(true); - sun_button->connect("pressed", callable_mp(this, &Node3DEditor::_update_preview_environment), varray(), CONNECT_DEFERRED); + sun_button->connect("pressed", callable_mp(this, &Node3DEditor::_update_preview_environment), CONNECT_DEFERRED); sun_button->set_disabled(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_toggle_mode(true); environ_button->set_flat(true); - environ_button->connect("pressed", callable_mp(this, &Node3DEditor::_update_preview_environment), varray(), CONNECT_DEFERRED); + environ_button->connect("pressed", callable_mp(this, &Node3DEditor::_update_preview_environment), CONNECT_DEFERRED); environ_button->set_disabled(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_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); @@ -7685,7 +7916,7 @@ Node3DEditor::Node3DEditor() { 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); @@ -7697,21 +7928,18 @@ Node3DEditor::Node3DEditor() { 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(); @@ -7829,7 +8057,7 @@ Node3DEditor::Node3DEditor() { 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 */ @@ -7954,8 +8182,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_uniform("sun_direction", Vector3(0, 0, 1)); + sun_direction_material->set_shader_uniform("sun_color", Vector3(1, 1, 1)); sun_direction->set_material(sun_direction_material); HBoxContainer *sun_angle_hbox = memnew(HBoxContainer); @@ -7991,6 +8219,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); @@ -8006,7 +8235,7 @@ 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->connect("pressed", callable_mp(this, &Node3DEditor::_add_sun_to_scene).bind(false)); sun_vb->add_spacer(); sun_vb->add_child(sun_add_to_scene); @@ -8036,10 +8265,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)); @@ -8051,29 +8282,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->connect("pressed", callable_mp(this, &Node3DEditor::_add_environment_to_scene).bind(false)); environ_vb->add_spacer(); environ_vb->add_child(environ_add_to_scene); @@ -8100,7 +8331,6 @@ void fragment() { _preview_settings_changed(); } } - Node3DEditor::~Node3DEditor() { memdelete(preview_node); } @@ -8121,7 +8351,13 @@ void Node3DEditorPlugin::edit(Object *p_object) { } bool Node3DEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("Node3D"); + if (p_object->is_class("Node3D")) { + return true; + } else { + // This ensures that gizmos are cleared when selecting a non-Node3D node. + const_cast<Node3DEditorPlugin *>(this)->edit((Object *)nullptr); + return false; + } } Dictionary Node3DEditorPlugin::get_state() const { diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index d5d50c743c..e0298ebd5f 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -53,6 +53,7 @@ class Node3DEditorViewport; class SubViewportContainer; class DirectionalLight3D; class WorldEnvironment; +class EditorUndoRedoManager; class ViewportRotationControl : public Control { GDCLASS(ViewportRotationControl, Control); @@ -141,6 +142,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, @@ -200,7 +202,7 @@ private: EditorData *editor_data = nullptr; EditorSelection *editor_selection = nullptr; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; CheckBox *preview_camera = nullptr; SubViewportContainer *subviewport_container = nullptr; @@ -211,7 +213,7 @@ private: Control *surface = nullptr; SubViewport *viewport = nullptr; Camera3D *camera = nullptr; - bool transforming; + bool transforming = false; bool orthogonal; bool auto_orthogonal; bool lock_rotation; @@ -226,6 +228,9 @@ private: Label *locked_label = nullptr; Label *zoom_limit_label = nullptr; + Label *preview_material_label = nullptr; + Label *preview_material_label_desc = nullptr; + VBoxContainer *top_right_vbox = nullptr; ViewportRotationControl *rotation_control = nullptr; Gradient *frame_time_gradient = nullptr; @@ -243,7 +248,7 @@ 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; @@ -251,6 +256,7 @@ private: 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; @@ -270,8 +276,9 @@ 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 = nullptr; @@ -397,8 +404,11 @@ 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(); @@ -461,7 +471,7 @@ public: 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; @@ -551,14 +561,14 @@ 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; + Camera3D::ProjectionType grid_camera_last_update_perspective = Camera3D::PROJECTION_PERSPECTIVE; Vector3 grid_camera_last_update_position = Vector3(); Ref<ArrayMesh> move_gizmo[3], move_plane_gizmo[3], rotate_gizmo[4], scale_gizmo[3], scale_plane_gizmo[3], axis_gizmo[3]; @@ -591,6 +601,11 @@ private: 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; @@ -662,19 +677,17 @@ 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 = 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 = nullptr; - HBoxContainer *hbc_context_menu = nullptr; + PanelContainer *context_menu_panel = nullptr; + HBoxContainer *context_menu_hbox = nullptr; void _generate_selection_boxes(); - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> 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(); @@ -808,13 +821,13 @@ 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 set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo); + Ref<EditorUndoRedoManager> get_undo_redo(); void add_control_to_menu_panel(Control *p_control); void remove_control_from_menu_panel(Control *p_control); @@ -850,6 +863,15 @@ public: 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]; diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.cpp b/editor/plugins/occluder_instance_3d_editor_plugin.cpp index 79cf4c394e..365f74d7a3 100644 --- a/editor/plugins/occluder_instance_3d_editor_plugin.cpp +++ b/editor/plugins/occluder_instance_3d_editor_plugin.cpp @@ -63,7 +63,7 @@ void OccluderInstance3DEditorPlugin::_bake_select_file(const String &p_file) { 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); + EditorNode::get_singleton()->show_warning(TTR("Could not save the new occluder at the specified path:") + " " + p_file); break; } default: { @@ -113,7 +113,7 @@ OccluderInstance3DEditorPlugin::OccluderInstance3DEditorPlugin() { 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 360b7297cf..e8d98927f4 100644 --- a/editor/plugins/occluder_instance_3d_editor_plugin.h +++ b/editor/plugins/occluder_instance_3d_editor_plugin.h @@ -63,4 +63,4 @@ public: ~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 ffa74173be..0000000000 --- a/editor/plugins/ot_features_plugin.cpp +++ /dev/null @@ -1,231 +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 "scene/3d/label_3d.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) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: - case 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->add_theme_color_override("label_color", base); - } break; - } -} - -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) { - edited_object->set("opentype_features/" + TS->tag_to_name(p_option), 1); -} - -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::setup(Object *p_object) { - edited_object = p_object; - - menu->clear(); - menu_ss->clear(); - menu_cv->clear(); - menu_cu->clear(); - bool have_ss = false; - bool have_cv = false; - bool have_cu = false; - - Ref<Font> font; - - Control *ctrl = Object::cast_to<Control>(edited_object); - if (ctrl != nullptr) { - font = ctrl->get_theme_font(SNAME("font")); - } else { - Label3D *l3d = Object::cast_to<Label3D>(edited_object); - if (l3d != nullptr) { - font = l3d->_get_font_or_default(); - } - } - - if (font.is_null()) { - return; - } - - Dictionary features = 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::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - connect("pressed", callable_mp(this, &OpenTypeFeaturesAdd::_features_menu)); - [[fallthrough]]; - } - case NOTIFICATION_THEME_CHANGED: { - set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - } break; - } -} - -void OpenTypeFeaturesAdd::_bind_methods() { -} - -OpenTypeFeaturesAdd::OpenTypeFeaturesAdd() { - set_text(TTR("Add Feature...")); - - 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); - - 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) || (Object::cast_to<Label3D>(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); - editor->setup(p_object); - add_custom_control(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() { - 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 6639148080..0000000000 --- a/editor/plugins/ot_features_plugin.h +++ /dev/null @@ -1,102 +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_plugin.h" -#include "editor/editor_properties.h" - -/*************************************************************************/ - -class OpenTypeFeaturesEditor : public EditorProperty { - GDCLASS(OpenTypeFeaturesEditor, EditorProperty); - EditorSpinSlider *spin = nullptr; - 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 Button { - GDCLASS(OpenTypeFeaturesAdd, Button); - - Object *edited_object = 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: - void setup(Object *p_object); - - 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(); - - virtual String get_name() const override { return "OpenTypeFeatures"; } -}; - -#endif // OT_FEATURES_PLUGIN_H diff --git a/editor/plugins/packed_scene_translation_parser_plugin.cpp b/editor/plugins/packed_scene_translation_parser_plugin.cpp index 9a8584f4a2..8d083d28b2 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.cpp +++ b/editor/plugins/packed_scene_translation_parser_plugin.cpp @@ -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; diff --git a/editor/plugins/packed_scene_translation_parser_plugin.h b/editor/plugins/packed_scene_translation_parser_plugin.h index ecd090b31b..1bfb500933 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.h +++ b/editor/plugins/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<String> lookup_properties; + HashSet<String> lookup_properties; // Properties from specific Nodes that should be ignored. - Map<String, Vector<String>> 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 9e666ef70a..1d8a3f5c81 100644 --- a/editor/plugins/path_2d_editor_plugin.cpp +++ b/editor/plugins/path_2d_editor_plugin.cpp @@ -36,6 +36,7 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" void Path2DEditor::_notification(int p_what) { switch (p_what) { @@ -537,7 +538,7 @@ Path2DEditor::Path2DEditor() { 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->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT)); base_hb->add_child(curve_edit); curve_edit_curve = memnew(Button); @@ -545,7 +546,7 @@ Path2DEditor::Path2DEditor() { 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->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT_CURVE)); base_hb->add_child(curve_edit_curve); curve_create = memnew(Button); @@ -553,7 +554,7 @@ Path2DEditor::Path2DEditor() { 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->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CREATE)); base_hb->add_child(curve_create); curve_del = memnew(Button); @@ -561,14 +562,14 @@ Path2DEditor::Path2DEditor() { 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->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_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->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(ACTION_CLOSE)); base_hb->add_child(curve_close); PopupMenu *menu; diff --git a/editor/plugins/path_2d_editor_plugin.h b/editor/plugins/path_2d_editor_plugin.h index ff74aeedf7..13eca79010 100644 --- a/editor/plugins/path_2d_editor_plugin.h +++ b/editor/plugins/path_2d_editor_plugin.h @@ -36,11 +36,12 @@ #include "scene/gui/separator.h" class CanvasItemEditor; +class EditorUndoRedoManager; class Path2DEditor : public HBoxContainer { GDCLASS(Path2DEditor, HBoxContainer); - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; CanvasItemEditor *canvas_item_editor = nullptr; Panel *panel = nullptr; @@ -82,11 +83,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); diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 3284af7bb5..084c0c2bb0 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -34,9 +34,24 @@ #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/resources/curve.h" +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()) { @@ -47,12 +62,10 @@ String Path3DGizmo::get_handle_name(int p_id, bool p_secondary) const { return TTR("Curve Point #") + itos(p_id); } - p_id += 1; // Account for the first point only having an "out" handle - - 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"; @@ -72,13 +85,11 @@ Variant Path3DGizmo::get_handle_value(int p_id, bool p_secondary) const { return original; } - p_id += 1; // Account for the first point only having an "out" handle - - 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); @@ -102,7 +113,7 @@ void Path3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, con // Setting curve point positions if (!p_secondary) { - const Plane p = Plane(p_camera->get_transform().basis.get_axis(2), gt.xform(original)); + const Plane p = Plane(p_camera->get_transform().basis.get_column(2), gt.xform(original)); Vector3 inters; @@ -119,14 +130,12 @@ void Path3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, con return; } - p_id += 1; // Account for the first point only having an "out" handle - - 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; @@ -144,7 +153,7 @@ void Path3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, con 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)); @@ -164,7 +173,7 @@ void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_res return; } - UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); if (!p_secondary) { if (p_cancel) { @@ -179,12 +188,10 @@ void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_res return; } - p_id += 1; // Account for the first point only having an "out" handle - - 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; @@ -263,17 +270,17 @@ void Path3DGizmo::redraw() { 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)); - } - + // 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)); } + 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)); + } } if (v3p.size() > 1) { @@ -379,7 +386,7 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera } } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); if (closest_seg != -1) { //subdivide @@ -396,7 +403,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); @@ -421,21 +428,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 = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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 = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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 = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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)); @@ -514,7 +521,7 @@ void Path3DEditorPlugin::_close_curve() { if (c->get_point_position(0) == c->get_point_position(c->get_point_count() - 1)) { return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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()); @@ -552,9 +559,9 @@ void Path3DEditorPlugin::_update_theme() { void Path3DEditorPlugin::_notification(int p_what) { switch (p_what) { case 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_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(); diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h index 72c24732c0..53e4e2efa8 100644 --- a/editor/plugins/path_3d_editor_plugin.h +++ b/editor/plugins/path_3d_editor_plugin.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef PATH_EDITOR_PLUGIN_H -#define PATH_EDITOR_PLUGIN_H +#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" @@ -84,7 +84,7 @@ class Path3DEditorPlugin : public EditorPlugin { 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; @@ -118,4 +118,4 @@ public: ~Path3DEditorPlugin(); }; -#endif // PATH_EDITOR_PLUGIN_H +#endif // PATH_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/physical_bone_3d_editor_plugin.h b/editor/plugins/physical_bone_3d_editor_plugin.h index 93e722a432..f15eab7991 100644 --- a/editor/plugins/physical_bone_3d_editor_plugin.h +++ b/editor/plugins/physical_bone_3d_editor_plugin.h @@ -28,8 +28,8 @@ /* 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_plugin.h" #include "scene/3d/physics_body_3d.h" @@ -76,4 +76,4 @@ public: 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 8566e92b22..4f46c99a04 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -192,7 +192,7 @@ 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(); @@ -455,7 +455,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { } 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; @@ -970,7 +970,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); @@ -1238,7 +1238,7 @@ Polygon2DEditor::Polygon2DEditor() { 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->connect("pressed", callable_mp(this, &Polygon2DEditor::_menu_option).bind(MODE_EDIT_UV)); uv_mode = UV_MODE_EDIT_POINT; uv_edit = memnew(AcceptDialog); @@ -1276,10 +1276,10 @@ Polygon2DEditor::Polygon2DEditor() { 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)); @@ -1289,7 +1289,7 @@ Polygon2DEditor::Polygon2DEditor() { 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); } diff --git a/editor/plugins/polygon_2d_editor_plugin.h b/editor/plugins/polygon_2d_editor_plugin.h index 4403d1e9c7..d878d3f9af 100644 --- a/editor/plugins/polygon_2d_editor_plugin.h +++ b/editor/plugins/polygon_2d_editor_plugin.h @@ -96,7 +96,7 @@ class Polygon2DEditor : public AbstractPolygon2DEditor { 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 = nullptr; @@ -110,7 +110,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; diff --git a/editor/plugins/polygon_3d_editor_plugin.cpp b/editor/plugins/polygon_3d_editor_plugin.cpp index 2d4812c55b..2b3a5c3e23 100644 --- a/editor/plugins/polygon_3d_editor_plugin.cpp +++ b/editor/plugins/polygon_3d_editor_plugin.cpp @@ -38,6 +38,7 @@ #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" @@ -117,7 +118,7 @@ EditorPlugin::AfterGUIInput Polygon3DEditor::forward_spatial_gui_input(Camera3D 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; @@ -532,13 +533,13 @@ Polygon3DEditor::Polygon3DEditor() { button_create = memnew(Button); button_create->set_flat(true); add_child(button_create); - button_create->connect("pressed", callable_mp(this, &Polygon3DEditor::_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, &Polygon3DEditor::_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; diff --git a/editor/plugins/polygon_3d_editor_plugin.h b/editor/plugins/polygon_3d_editor_plugin.h index 3ad7a4df58..0eb02a39e2 100644 --- a/editor/plugins/polygon_3d_editor_plugin.h +++ b/editor/plugins/polygon_3d_editor_plugin.h @@ -37,11 +37,12 @@ #include "scene/resources/immediate_mesh.h" class CanvasItemEditor; +class EditorUndoRedoManager; class Polygon3DEditor : public HBoxContainer { GDCLASS(Polygon3DEditor, HBoxContainer); - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; enum Mode { MODE_CREATE, MODE_EDIT, @@ -66,14 +67,14 @@ class Polygon3DEditor : public HBoxContainer { MenuButton *options = nullptr; - int edited_point; + int edited_point = 0; Vector2 edited_point_pos; 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(); diff --git a/editor/plugins/replication_editor_plugin.cpp b/editor/plugins/replication_editor_plugin.cpp deleted file mode 100644 index 2a7b3c7a55..0000000000 --- a/editor/plugins/replication_editor_plugin.cpp +++ /dev/null @@ -1,396 +0,0 @@ -/*************************************************************************/ -/* replication_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 "replication_editor_plugin.h" - -#include "editor/editor_node.h" -#include "editor/editor_scale.h" -#include "editor/inspector_dock.h" -#include "scene/gui/dialogs.h" -#include "scene/gui/tree.h" -#include "scene/multiplayer/multiplayer_synchronizer.h" - -/// ReplicationEditor -ReplicationEditor::ReplicationEditor() { - set_v_size_flags(SIZE_EXPAND_FILL); - set_custom_minimum_size(Size2(0, 200) * EDSCALE); - - delete_dialog = memnew(ConfirmationDialog); - delete_dialog->connect("cancelled", callable_mp(this, &ReplicationEditor::_dialog_closed), varray(false)); - delete_dialog->connect("confirmed", callable_mp(this, &ReplicationEditor::_dialog_closed), varray(true)); - add_child(delete_dialog); - - error_dialog = memnew(AcceptDialog); - error_dialog->get_ok_button()->set_text(TTR("Close")); - error_dialog->set_title(TTR("Error!")); - add_child(error_dialog); - - VBoxContainer *vb = memnew(VBoxContainer); - vb->set_v_size_flags(SIZE_EXPAND_FILL); - add_child(vb); - - HBoxContainer *hb = memnew(HBoxContainer); - vb->add_child(hb); - np_line_edit = memnew(LineEdit); - np_line_edit->set_placeholder(":property"); - np_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); - hb->add_child(np_line_edit); - add_button = memnew(Button); - add_button->connect("pressed", callable_mp(this, &ReplicationEditor::_add_pressed)); - add_button->set_text(TTR("Add")); - hb->add_child(add_button); - - tree = memnew(Tree); - tree->set_hide_root(true); - tree->set_columns(4); - tree->set_column_titles_visible(true); - tree->set_column_title(0, TTR("Properties")); - tree->set_column_expand(0, true); - tree->set_column_title(1, TTR("Spawn")); - tree->set_column_expand(1, false); - tree->set_column_custom_minimum_width(1, 100); - tree->set_column_title(2, TTR("Sync")); - tree->set_column_custom_minimum_width(2, 100); - tree->set_column_expand(2, false); - tree->set_column_expand(3, false); - tree->create_item(); - tree->connect("button_pressed", callable_mp(this, &ReplicationEditor::_tree_button_pressed)); - tree->connect("item_edited", callable_mp(this, &ReplicationEditor::_tree_item_edited)); - tree->set_v_size_flags(SIZE_EXPAND_FILL); - vb->add_child(tree); -} - -void ReplicationEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_update_config"), &ReplicationEditor::_update_config); - ClassDB::bind_method(D_METHOD("_update_checked", "property", "column", "checked"), &ReplicationEditor::_update_checked); - ADD_SIGNAL(MethodInfo("keying_changed")); -} - -void ReplicationEditor::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: - case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); - } break; - - case NOTIFICATION_VISIBILITY_CHANGED: { - update_keying(); - } break; - } -} - -void ReplicationEditor::_add_pressed() { - if (!current) { - error_dialog->set_text(TTR("Please select a MultiplayerSynchronizer first.")); - error_dialog->popup_centered(); - return; - } - if (current->get_root_path().is_empty()) { - error_dialog->set_text(TTR("The MultiplayerSynchronizer needs a root path.")); - error_dialog->popup_centered(); - return; - } - String np_text = np_line_edit->get_text(); - if (np_text.find(":") == -1) { - np_text = ":" + np_text; - } - NodePath prop = NodePath(np_text); - if (prop.is_empty()) { - return; - } - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); - undo_redo->create_action(TTR("Add property")); - config = current->get_replication_config(); - if (config.is_null()) { - config.instantiate(); - current->set_replication_config(config); - undo_redo->add_do_method(current, "set_replication_config", config); - undo_redo->add_undo_method(current, "set_replication_config", Ref<SceneReplicationConfig>()); - _update_config(); - } - undo_redo->add_do_method(config.ptr(), "add_property", prop); - undo_redo->add_undo_method(config.ptr(), "remove_property", prop); - undo_redo->add_do_method(this, "_update_config"); - undo_redo->add_undo_method(this, "_update_config"); - undo_redo->commit_action(); -} - -void ReplicationEditor::_tree_item_edited() { - TreeItem *ti = tree->get_edited(); - if (!ti || config.is_null()) { - return; - } - int column = tree->get_edited_column(); - ERR_FAIL_COND(column < 1 || column > 2); - const NodePath prop = ti->get_metadata(0); - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); - bool value = ti->is_checked(column); - String method; - if (column == 1) { - undo_redo->create_action(TTR("Set spawn property")); - method = "property_set_spawn"; - } else { - undo_redo->create_action(TTR("Set sync property")); - method = "property_set_sync"; - } - undo_redo->add_do_method(config.ptr(), method, prop, value); - undo_redo->add_undo_method(config.ptr(), method, prop, !value); - undo_redo->add_do_method(this, "_update_checked", prop, column, value); - undo_redo->add_undo_method(this, "_update_checked", prop, column, !value); - undo_redo->commit_action(); -} - -void ReplicationEditor::_tree_button_pressed(Object *p_item, int p_column, int p_id) { - TreeItem *ti = Object::cast_to<TreeItem>(p_item); - if (!ti) { - return; - } - deleting = ti->get_metadata(0); - delete_dialog->set_text(TTR("Delete Property?") + "\n\"" + ti->get_text(0) + "\""); - delete_dialog->popup_centered(); -} - -void ReplicationEditor::_dialog_closed(bool p_confirmed) { - if (deleting.is_empty() || config.is_null()) { - return; - } - if (p_confirmed) { - const NodePath prop = deleting; - int idx = config->property_get_index(prop); - bool spawn = config->property_get_spawn(prop); - bool sync = config->property_get_sync(prop); - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); - undo_redo->create_action(TTR("Remove Property")); - undo_redo->add_do_method(config.ptr(), "remove_property", prop); - undo_redo->add_undo_method(config.ptr(), "add_property", prop, idx); - undo_redo->add_undo_method(config.ptr(), "property_set_spawn", prop, spawn); - undo_redo->add_undo_method(config.ptr(), "property_set_sync", prop, sync); - undo_redo->add_do_method(this, "_update_config"); - undo_redo->add_undo_method(this, "_update_config"); - undo_redo->commit_action(); - } - deleting = NodePath(); -} - -void ReplicationEditor::_update_checked(const NodePath &p_prop, int p_column, bool p_checked) { - if (!tree->get_root()) { - return; - } - TreeItem *ti = tree->get_root()->get_first_child(); - while (ti) { - if (ti->get_metadata(0).operator NodePath() == p_prop) { - ti->set_checked(p_column, p_checked); - return; - } - ti = ti->get_next(); - } -} - -void ReplicationEditor::update_keying() { - /// TODO make keying usable. -#if 0 - bool keying_enabled = false; - EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history(); - if (is_visible_in_tree() && config.is_valid() && editor_history->get_path_size() > 0) { - Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0)); - keying_enabled = Object::cast_to<Node>(obj) != nullptr; - } - - if (keying_enabled == keying) { - return; - } - - keying = keying_enabled; - emit_signal(SNAME("keying_changed")); -#endif -} - -void ReplicationEditor::_update_config() { - deleting = NodePath(); - tree->clear(); - tree->create_item(); - if (!config.is_valid()) { - update_keying(); - return; - } - TypedArray<NodePath> props = config->get_properties(); - for (int i = 0; i < props.size(); i++) { - const NodePath path = props[i]; - _add_property(path, config->property_get_spawn(path), config->property_get_sync(path)); - } - update_keying(); -} - -void ReplicationEditor::edit(MultiplayerSynchronizer *p_sync) { - if (current == p_sync) { - return; - } - current = p_sync; - if (current) { - config = current->get_replication_config(); - } else { - config.unref(); - } - _update_config(); -} - -Ref<Texture2D> ReplicationEditor::_get_class_icon(const Node *p_node) { - if (!p_node || !has_theme_icon(p_node->get_class(), "EditorIcons")) { - return get_theme_icon(SNAME("ImportFail"), SNAME("EditorIcons")); - } - return get_theme_icon(p_node->get_class(), "EditorIcons"); -} - -void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn, bool p_sync) { - String prop = String(p_property); - TreeItem *item = tree->create_item(); - item->set_selectable(0, false); - item->set_selectable(1, false); - item->set_selectable(2, false); - item->set_selectable(3, false); - item->set_text(0, prop); - item->set_metadata(0, prop); - Node *root_node = current && !current->get_root_path().is_empty() ? current->get_node(current->get_root_path()) : nullptr; - Ref<Texture2D> icon = _get_class_icon(root_node); - if (root_node) { - String path = prop.substr(0, prop.find(":")); - String subpath = prop.substr(path.size()); - Node *node = root_node->get_node_or_null(path); - if (!node) { - node = root_node; - } - item->set_text(0, String(node->get_name()) + ":" + subpath); - icon = _get_class_icon(node); - } - item->set_icon(0, icon); - item->add_button(3, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - item->set_text_alignment(1, HORIZONTAL_ALIGNMENT_CENTER); - item->set_cell_mode(1, TreeItem::CELL_MODE_CHECK); - item->set_checked(1, p_spawn); - item->set_editable(1, true); - item->set_text_alignment(2, HORIZONTAL_ALIGNMENT_CENTER); - item->set_cell_mode(2, TreeItem::CELL_MODE_CHECK); - item->set_checked(2, p_sync); - item->set_editable(2, true); -} - -void ReplicationEditor::property_keyed(const String &p_property) { - ERR_FAIL_COND(!current || config.is_null()); - Node *root = current->get_node(current->get_root_path()); - ERR_FAIL_COND(!root); - EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history(); - ERR_FAIL_COND(history->get_path_size() == 0); - Node *node = Object::cast_to<Node>(ObjectDB::get_instance(history->get_path_object(0))); - ERR_FAIL_COND(!node); - if (node->is_class("MultiplayerSynchronizer")) { - error_dialog->set_text(TTR("Properties of 'MultiplayerSynchronizer' cannot be configured for replication.")); - error_dialog->popup_centered(); - return; - } - if (history->get_path_size() > 1 || p_property.get_slice_count(":") > 1) { - error_dialog->set_text(TTR("Subresources cannot yet be configured for replication.")); - error_dialog->popup_centered(); - return; - } - - String path = root->get_path_to(node); - for (int i = 1; i < history->get_path_size(); i++) { - String prop = history->get_path_property(i); - ERR_FAIL_COND(prop == ""); - path += ":" + prop; - } - path += ":" + p_property; - - NodePath prop = path; - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); - undo_redo->create_action(TTR("Add property")); - undo_redo->add_do_method(config.ptr(), "add_property", prop); - undo_redo->add_undo_method(config.ptr(), "remove_property", prop); - undo_redo->add_do_method(this, "_update_config"); - undo_redo->add_undo_method(this, "_update_config"); - undo_redo->commit_action(); -} - -/// ReplicationEditorPlugin -ReplicationEditorPlugin::ReplicationEditorPlugin() { - repl_editor = memnew(ReplicationEditor); - EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor); -} - -ReplicationEditorPlugin::~ReplicationEditorPlugin() { -} - -void ReplicationEditorPlugin::_keying_changed() { - // TODO make lock usable. - //InspectorDock::get_inspector_singleton()->set_keying(repl_editor->has_keying(), this); -} - -void ReplicationEditorPlugin::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) { - if (!repl_editor->has_keying()) { - return; - } - repl_editor->property_keyed(p_keyed); -} - -void ReplicationEditorPlugin::_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, &ReplicationEditorPlugin::_property_keyed)); - repl_editor->connect("keying_changed", callable_mp(this, &ReplicationEditorPlugin::_keying_changed)); - // TODO make lock usable. - //InspectorDock::get_inspector_singleton()->connect("object_inspected", callable_mp(repl_editor, &ReplicationEditor::update_keying)); - get_tree()->connect("node_removed", callable_mp(this, &ReplicationEditorPlugin::_node_removed)); - } break; - } -} - -void ReplicationEditorPlugin::_node_removed(Node *p_node) { - if (p_node && p_node == repl_editor->get_current()) { - repl_editor->edit(nullptr); - if (repl_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->hide_bottom_panel(); - } - } -} - -void ReplicationEditorPlugin::edit(Object *p_object) { - repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object)); -} - -bool ReplicationEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("MultiplayerSynchronizer"); -} - -void ReplicationEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - EditorNode::get_singleton()->make_bottom_panel_item_visible(repl_editor); - } -} diff --git a/editor/plugins/replication_editor_plugin.h b/editor/plugins/replication_editor_plugin.h deleted file mode 100644 index 08e86d1617..0000000000 --- a/editor/plugins/replication_editor_plugin.h +++ /dev/null @@ -1,105 +0,0 @@ -/*************************************************************************/ -/* replication_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 REPLICATION_EDITOR_PLUGIN_H -#define REPLICATION_EDITOR_PLUGIN_H - -#include "editor/editor_plugin.h" -#include "scene/resources/scene_replication_config.h" - -class ConfirmationDialog; -class MultiplayerSynchronizer; -class Tree; - -class ReplicationEditor : public VBoxContainer { - GDCLASS(ReplicationEditor, VBoxContainer); - -private: - MultiplayerSynchronizer *current = nullptr; - - AcceptDialog *error_dialog = nullptr; - ConfirmationDialog *delete_dialog = nullptr; - Button *add_button = nullptr; - LineEdit *np_line_edit = nullptr; - - Ref<SceneReplicationConfig> config; - NodePath deleting; - Tree *tree = nullptr; - bool keying = false; - - Ref<Texture2D> _get_class_icon(const Node *p_node); - - void _add_pressed(); - void _tree_item_edited(); - void _tree_button_pressed(Object *p_item, int p_column, int p_id); - void _update_checked(const NodePath &p_prop, int p_column, bool p_checked); - void _update_config(); - void _dialog_closed(bool p_confirmed); - void _add_property(const NodePath &p_property, bool p_spawn = true, bool p_sync = true); - -protected: - static void _bind_methods(); - - void _notification(int p_what); - -public: - void update_keying(); - void edit(MultiplayerSynchronizer *p_object); - bool has_keying() const { return keying; } - MultiplayerSynchronizer *get_current() const { return current; } - void property_keyed(const String &p_property); - - ReplicationEditor(); - ~ReplicationEditor() {} -}; - -class ReplicationEditorPlugin : public EditorPlugin { - GDCLASS(ReplicationEditorPlugin, EditorPlugin); - -private: - ReplicationEditor *repl_editor = nullptr; - - void _node_removed(Node *p_node); - void _keying_changed(); - void _property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance); - -protected: - void _notification(int p_what); - -public: - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - ReplicationEditorPlugin(); - ~ReplicationEditorPlugin(); -}; - -#endif // REPLICATION_EDITOR_PLUGIN_H diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp index eafc53c72b..201a3af539 100644 --- a/editor/plugins/resource_preloader_editor_plugin.cpp +++ b/editor/plugins/resource_preloader_editor_plugin.cpp @@ -50,14 +50,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 } @@ -113,7 +113,7 @@ void ResourcePreloaderEditor::_item_edited() { return; } - RES samp = preloader->get_resource(old_name); + Ref<Resource> samp = preloader->get_resource(old_name); 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); @@ -135,11 +135,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 } @@ -190,7 +190,7 @@ 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()); @@ -213,7 +213,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); @@ -222,7 +226,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) { @@ -230,6 +234,10 @@ void ResourcePreloaderEditor::_cell_button_pressed(Object *p_item, int p_column, } } +void ResourcePreloaderEditor::set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo) { + undo_redo = p_undo_redo; +} + void ResourcePreloaderEditor::edit(ResourcePreloader *p_preloader) { preloader = p_preloader; @@ -249,7 +257,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(); } @@ -269,7 +277,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(); } @@ -294,7 +302,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; @@ -359,7 +367,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); @@ -383,7 +391,7 @@ ResourcePreloaderEditor::ResourcePreloaderEditor() { } void ResourcePreloaderEditorPlugin::edit(Object *p_object) { - preloader_editor->set_undo_redo(&get_undo_redo()); + preloader_editor->set_undo_redo(EditorNode::get_undo_redo()); ResourcePreloader *s = Object::cast_to<ResourcePreloader>(p_object); if (!s) { return; @@ -416,11 +424,8 @@ ResourcePreloaderEditorPlugin::ResourcePreloaderEditorPlugin() { preloader_editor = memnew(ResourcePreloaderEditor); preloader_editor->set_custom_minimum_size(Size2(0, 250) * EDSCALE); - button = EditorNode::get_singleton()->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 0b799c13c6..ef80283dae 100644 --- a/editor/plugins/resource_preloader_editor_plugin.h +++ b/editor/plugins/resource_preloader_editor_plugin.h @@ -37,6 +37,7 @@ #include "scene/main/resource_preloader.h" class EditorFileDialog; +class EditorUndoRedoManager; class ResourcePreloaderEditor : public PanelContainer { GDCLASS(ResourcePreloaderEditor, PanelContainer); @@ -63,10 +64,10 @@ class ResourcePreloaderEditor : public PanelContainer { 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 = nullptr; + Ref<EditorUndoRedoManager> 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; @@ -78,7 +79,7 @@ protected: static void _bind_methods(); public: - void set_undo_redo(UndoRedo *p_undo_redo) { undo_redo = p_undo_redo; } + void set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo); void edit(ResourcePreloader *p_preloader); ResourcePreloaderEditor(); diff --git a/editor/plugins/root_motion_editor_plugin.cpp b/editor/plugins/root_motion_editor_plugin.cpp index bfb672d694..681dd476e3 100644 --- a/editor/plugins/root_motion_editor_plugin.cpp +++ b/editor/plugins/root_motion_editor_plugin.cpp @@ -65,7 +65,7 @@ void EditorPropertyRootMotion::_node_assign() { return; } - Set<String> paths; + HashSet<String> paths; { List<StringName> animations; player->get_animation_list(&animations); @@ -81,10 +81,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++) { diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index a4bf28625d..a0c9ddb14b 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -41,6 +41,7 @@ #include "editor/debugger/script_editor_debugger.h" #include "editor/editor_file_dialog.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" @@ -48,7 +49,6 @@ #include "editor/find_in_files.h" #include "editor/node_dock.h" #include "editor/plugins/shader_editor_plugin.h" -#include "modules/visual_script/editor/visual_script_editor.h" #include "scene/main/window.h" #include "scene/scene_string_names.h" #include "script_text_editor.h" @@ -65,12 +65,12 @@ String EditorSyntaxHighlighter::_get_name() const { return "Unnamed"; } -Array EditorSyntaxHighlighter::_get_supported_languages() const { - Array ret; +PackedStringArray EditorSyntaxHighlighter::_get_supported_languages() const { + PackedStringArray ret; if (GDVIRTUAL_CALL(_get_supported_languages, ret)) { return ret; } - return Array(); + return PackedStringArray(); } Ref<EditorSyntaxHighlighter> EditorSyntaxHighlighter::_create() const { @@ -119,9 +119,9 @@ 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); } @@ -232,24 +232,24 @@ void ScriptEditorBase::_bind_methods() { 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()) { @@ -258,35 +258,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() {} @@ -377,7 +377,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); @@ -428,7 +428,7 @@ void ScriptEditor::_goto_script_line2(int p_line) { } } -void ScriptEditor::_goto_script_line(REF p_script, int p_line) { +void ScriptEditor::_goto_script_line(Ref<RefCounted> 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())) { if (edit(p_script, p_line, 0)) { @@ -444,7 +444,7 @@ void ScriptEditor::_goto_script_line(REF p_script, int p_line) { } } -void ScriptEditor::_set_execution(REF p_script, int p_line) { +void ScriptEditor::_set_execution(Ref<RefCounted> 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_tab_count(); i++) { @@ -460,7 +460,7 @@ void ScriptEditor::_set_execution(REF p_script, int p_line) { } } -void ScriptEditor::_clear_execution(REF p_script) { +void ScriptEditor::_clear_execution(Ref<RefCounted> 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_tab_count(); i++) { @@ -476,7 +476,7 @@ void ScriptEditor::_clear_execution(REF p_script) { } } -void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) { +void ScriptEditor::_set_breakpoint(Ref<RefCounted> 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())) { // Update if open. @@ -590,7 +590,7 @@ void ScriptEditor::_go_to_tab(int p_idx) { } } - Control *c = Object::cast_to<Control>(tab_container->get_tab_control(p_idx)); + Control *c = tab_container->get_tab_control(p_idx); if (!c) { return; } @@ -758,7 +758,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { 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). @@ -812,7 +812,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { if (history_pos >= 0) { 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(); } @@ -849,7 +849,7 @@ void ScriptEditor::_close_docs_tab() { void ScriptEditor::_copy_script_path() { ScriptEditorBase *se = _get_current_editor(); if (se) { - RES script = se->get_edited_resource(); + Ref<Resource> script = se->get_edited_resource(); DisplayServer::get_singleton()->clipboard_set(script->get_path()); } } @@ -882,7 +882,7 @@ void ScriptEditor::_queue_close_tabs() { // 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_ONESHOT); break; } } @@ -906,7 +906,7 @@ void ScriptEditor::_resave_scripts(const String &p_str) { continue; } - RES script = se->get_edited_resource(); + Ref<Resource> script = se->get_edited_resource(); if (script->is_built_in()) { continue; //internal script, who cares @@ -947,7 +947,7 @@ void ScriptEditor::_reload_scripts() { continue; } - RES edited_res = se->get_edited_resource(); + Ref<Resource> edited_res = se->get_edited_resource(); if (edited_res->is_built_in()) { continue; //internal script, who cares @@ -966,7 +966,7 @@ void ScriptEditor::_reload_scripts() { 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(); + script->reload(true); } Ref<TextFile> text_file = edited_res; @@ -991,7 +991,7 @@ void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { continue; } - RES script = se->get_edited_resource(); + Ref<Resource> script = se->get_edited_resource(); if (script == p_res) { se->tag_saved_version(); @@ -1010,7 +1010,7 @@ void ScriptEditor::_scene_saved_callback(const String &p_path) { 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. @@ -1039,7 +1039,7 @@ 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); @@ -1051,7 +1051,7 @@ bool ScriptEditor::_test_script_times_on_disk(RES p_for_script) { 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; } @@ -1117,7 +1117,7 @@ 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); @@ -1155,8 +1155,8 @@ Ref<Script> ScriptEditor::_get_current_script() { } } -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++) { @@ -1188,7 +1188,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...")); @@ -1203,11 +1203,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(); @@ -1323,7 +1323,7 @@ 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; @@ -1365,9 +1365,17 @@ void ScriptEditor::_menu_option(int p_option) { } } 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: { @@ -1376,6 +1384,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 @@ -1384,10 +1396,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.")); @@ -1399,8 +1407,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()) { @@ -1413,7 +1419,7 @@ void ScriptEditor::_menu_option(int p_option) { _copy_script_path(); } break; case SHOW_IN_FILE_SYSTEM: { - const RES script = current->get_edited_resource(); + const Ref<Resource> script = current->get_edited_resource(); String path = script->get_path(); if (!path.is_empty()) { if (script->is_built_in()) { @@ -1439,20 +1445,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_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(); } } @@ -1485,14 +1491,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_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; @@ -1534,7 +1540,7 @@ 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().plus_file(EditorSettings::get_singleton()->get("text_editor/theme/color_theme"))); file_dialog->popup_file_dialog(); file_dialog->set_title(TTR("Save Theme As...")); } @@ -1707,7 +1713,7 @@ void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) { } void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { - Set<String> loaded_scripts; + 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) { @@ -1725,7 +1731,7 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { 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)); } @@ -1792,7 +1798,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; } @@ -1844,10 +1850,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); } @@ -1902,6 +1910,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()); @@ -1950,11 +1959,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; @@ -1972,7 +1981,7 @@ 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); @@ -2116,8 +2125,8 @@ 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; } @@ -2145,8 +2154,10 @@ 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(); @@ -2188,7 +2199,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); @@ -2230,7 +2241,7 @@ Error ScriptEditor::_save_text_file(Ref<TextFile> p_text_file, const String &p_p 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; } @@ -2368,8 +2379,8 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra 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) { + PackedStringArray languages = highlighter->_get_supported_languages(); + if (languages.has(script->get_language()->get_name())) { se->set_syntax_highlighter(highlighter); highlighter_set = true; } @@ -2447,7 +2458,7 @@ 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; @@ -2516,7 +2527,7 @@ 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(); } @@ -2589,14 +2600,14 @@ void ScriptEditor::open_text_file_create_dialog(const String &p_base_path, const 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); if (extensions.find(p_file.get_extension())) { Ref<Script> scr = ResourceLoader::load(p_file); if (!scr.is_valid()) { EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); - return RES(); + return Ref<Resource>(); } edit(scr); @@ -2607,14 +2618,14 @@ RES ScriptEditor::open_file(const String &p_file) { Ref<TextFile> text_file = _load_text_file(p_file, &error); if (error != OK) { EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); - return RES(); + return Ref<Resource>(); } if (text_file.is_valid()) { edit(text_file); return text_file; } - return RES(); + return Ref<Resource>(); } void ScriptEditor::_editor_stop() { @@ -2980,7 +2991,7 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co continue; } - RES res = open_file(file); + Ref<Resource> res = open_file(file); if (res.is_valid()) { if (tab_container->get_tab_count() > num_tabs_before) { tab_container->move_child(tab_container->get_tab_control(tab_container->get_tab_count() - 1), new_index); @@ -3091,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(); } @@ -3135,7 +3146,7 @@ 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); @@ -3256,7 +3267,7 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { 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().plus_file("script_editor_cache.cfg")); } void ScriptEditor::_help_class_open(const String &p_class) { @@ -3434,8 +3445,8 @@ Vector<Ref<Script>> ScriptEditor::get_open_scripts() const { return out_scripts; } -Array ScriptEditor::_get_open_script_editors() const { - Array script_editors; +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) { @@ -3533,13 +3544,13 @@ 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") { EditorNode::get_singleton()->load_scene(fpath); @@ -3637,7 +3648,7 @@ 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().plus_file("script_editor_cache.cfg")); completion_cache = memnew(EditorScriptCodeCompletionCache); restoring_layout = false; @@ -3666,7 +3677,7 @@ ScriptEditor::ScriptEditor() { 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); @@ -3677,7 +3688,7 @@ ScriptEditor::ScriptEditor() { 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("gui_input", callable_mp(this, &ScriptEditor::_script_list_gui_input), CONNECT_DEFERRED); script_list->set_allow_rmb_select(true); script_list->set_drag_forwarding(this); @@ -3709,7 +3720,7 @@ ScriptEditor::ScriptEditor() { 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); @@ -3774,7 +3785,7 @@ ScriptEditor::ScriptEditor() { 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 | 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(); @@ -3845,14 +3856,14 @@ ScriptEditor::ScriptEditor() { 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.")); 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.")); @@ -3875,9 +3886,9 @@ ScriptEditor::ScriptEditor() { 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); @@ -3908,7 +3919,7 @@ ScriptEditor::ScriptEditor() { 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->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)); @@ -3931,8 +3942,8 @@ ScriptEditor::ScriptEditor() { 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 = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Search Results"), find_in_files); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 7885ffe2c5..d1898efb69 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -53,20 +53,20 @@ 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; }; @@ -136,9 +136,9 @@ 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 set_edited_resource(const Ref<Resource> &p_res) = 0; virtual void enable_editor() = 0; virtual void reload_text() = 0; virtual String get_name() = 0; @@ -156,7 +156,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; @@ -179,7 +179,7 @@ public: ScriptEditorBase() {} }; -typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const RES &p_resource); +typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const Ref<Resource> &p_resource); class EditorScriptCodeCompletionCache; class FindInFilesDialog; @@ -206,7 +206,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, @@ -249,7 +248,7 @@ class ScriptEditor : public PanelContainer { MenuButton *debug_menu = nullptr; PopupMenu *context_menu = nullptr; Timer *autosave_timer = nullptr; - uint64_t idle; + uint64_t idle = 0; PopupMenu *recent_scripts = nullptr; PopupMenu *theme_submenu = nullptr; @@ -330,7 +329,7 @@ class ScriptEditor : public PanelContainer { 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(); @@ -377,17 +376,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); @@ -415,7 +414,7 @@ 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(); @@ -453,9 +452,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); @@ -481,12 +480,12 @@ public: void apply_scripts() const; 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); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index f581d6c928..5e7db17edf 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -133,11 +133,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()); @@ -596,7 +596,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; } @@ -664,7 +664,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; } @@ -686,15 +686,15 @@ void ScriptEditor::_update_modified_scripts_for_external_editor(Ref<Script> p_fo 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> script = E; if (p_for_script.is_valid() && p_for_script != script) { continue; @@ -751,7 +751,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; } @@ -855,6 +855,21 @@ 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::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; @@ -871,6 +886,9 @@ 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::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; @@ -955,7 +973,7 @@ void ScriptTextEditor::_update_connected_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); @@ -1181,7 +1199,7 @@ void ScriptTextEditor::_edit_option(int p_op) { 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); + Variant result = expression.execute(Array(), Variant(), false, true); if (expression.get_error_text().is_empty()) { results.push_back(whitespace + result.get_construct_string()); } else { @@ -1246,7 +1264,7 @@ void ScriptTextEditor::_edit_option(int p_op) { EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), line + 1, dobreak); } 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]; @@ -1256,7 +1274,7 @@ 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; } @@ -1282,7 +1300,7 @@ void ScriptTextEditor::_edit_option(int p_op) { } break; case DEBUG_GOTO_PREV_BREAKPOINT: { - Array bpoints = tx->get_breakpointed_lines(); + PackedInt32Array bpoints = tx->get_breakpointed_lines(); if (bpoints.size() <= 0) { return; } @@ -1356,11 +1374,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(); @@ -1402,7 +1420,9 @@ Control *ScriptTextEditor::get_edit_menu() { } void ScriptTextEditor::clear_edit_menu() { - memdelete(edit_hb); + if (editor_enabled) { + memdelete(edit_hb); + } } void ScriptTextEditor::set_find_replace_bar(FindReplaceBar *p_bar) { @@ -1421,7 +1441,7 @@ void ScriptTextEditor::reload(bool p_soft) { 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(); } @@ -1436,7 +1456,7 @@ void ScriptTextEditor::clear_breakpoints() { 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.bind(argp, 1)); + code_editor->get_text_editor()->set_tooltip_request_func(p_toolip_callback.bindp(argp, 1)); } void ScriptTextEditor::set_debugger_active(bool p_active) { @@ -1509,6 +1529,7 @@ 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")) { @@ -1531,6 +1552,7 @@ 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(text_to_drop); + te->grab_focus(); } if (d.has("type") && String(d["type"]) == "nodes") { @@ -1543,24 +1565,73 @@ 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 = path.c_escape().quote(quote_style); + break; + } + } + + String variable_name = String(node->get_name()).camelcase_to_underscore(true).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 = path.c_escape().quote(quote_style); + 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") { @@ -1569,6 +1640,7 @@ 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(text_to_drop); + te->grab_focus(); } } @@ -1751,7 +1823,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); @@ -1790,20 +1862,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); @@ -1892,7 +1954,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); @@ -1998,7 +2060,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); } diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index c1c4b0af54..8d2fb98721 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -64,7 +64,7 @@ class ScriptTextEditor : public ScriptEditorBase { Vector<String> functions; List<ScriptLanguage::Warning> warnings; List<ScriptLanguage::ScriptError> errors; - Set<int> safe_lines; + HashSet<int> safe_lines; List<Connection> missing_connections; @@ -175,7 +175,7 @@ protected: 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); @@ -205,8 +205,8 @@ 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 Ref<Resource> get_edited_resource() const override; + virtual void set_edited_resource(const Ref<Resource> &p_res) override; virtual void enable_editor() override; virtual Vector<String> get_functions() override; virtual void reload_text() override; @@ -229,7 +229,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; diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 1bf78cc107..4641df3dca 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -38,16 +38,64 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/filesystem_dock.h" +#include "editor/plugins/visual_shader_editor_plugin.h" #include "editor/project_settings_editor.h" -#include "editor/property_editor.h" +#include "editor/shader_create_dialog.h" +#include "scene/gui/split_container.h" #include "servers/display_server.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 Map<ShaderWarning::Code, bool> saved_warnings; +static HashMap<ShaderWarning::Code, bool> saved_warnings; static uint32_t saved_warning_flags = 0U; void ShaderTextEditor::_notification(int p_what) { @@ -68,18 +116,69 @@ 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_shader->get_code()); + 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(); @@ -128,11 +227,12 @@ void ShaderTextEditor::_load_theme_settings() { 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"); + 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); @@ -141,11 +241,41 @@ void ShaderTextEditor::_load_theme_settings() { } } + 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.is_valid()) { + + 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 &info = modes[j]; + + if (!info.options.is_empty()) { + for (int k = 0; k < info.options.size(); k++) { + built_ins.push_back(String(info.name) + "_" + String(info.options[k])); + } + } else { + built_ins.push_back(String(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); @@ -178,6 +308,7 @@ void ShaderTextEditor::_load_theme_settings() { syntax_highlighter->clear_color_regions(); syntax_highlighter->add_color_region("/*", "*/", comment_color, false); syntax_highlighter->add_color_region("//", "", comment_color, true); + syntax_highlighter->set_disabled_branch_color(comment_color); text_editor->clear_comment_delimiters(); text_editor->add_comment_delimiter("/*", "*/", false); @@ -187,8 +318,12 @@ void ShaderTextEditor::_load_theme_settings() { text_editor->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. 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"))); } @@ -212,82 +347,222 @@ void ShaderTextEditor::_check_shader_mode() { } 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_variable_type(const StringName &p_variable) { - RS::GlobalVariableType gvt = RS::get_singleton()->global_variable_get_type(p_variable); - return (ShaderLanguage::DataType)RS::global_variable_type_get_shader_datatype(gvt); +static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) { + RS::GlobalShaderUniformType gvt = RS::get_singleton()->global_shader_uniform_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) { - _check_shader_mode(); + List<ScriptLanguage::CodeCompletionOption> pp_options; + ShaderPreprocessor preprocessor; + String code; + complete_from_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path()).get_base_dir(); + if (!complete_from_path.ends_with("/")) { + complete_from_path += "/"; + } + preprocessor.preprocess(p_code, "", code, nullptr, nullptr, nullptr, nullptr, &pp_options, _complete_include_paths); + complete_from_path = String(); + if (pp_options.size()) { + for (const ScriptLanguage::CodeCompletionOption &E : pp_options) { + r_options->push_back(E); + } + return; + } ShaderLanguage sl; String calltip; - ShaderLanguage::ShaderCompileInfo info; + info.global_shader_uniform_type_func = _get_global_shader_uniform_type; + + if (shader.is_null()) { + info.is_include = true; + + sl.complete(code, info, r_options, calltip); + get_text_editor()->set_code_hint(calltip); + return; + } + _check_shader_mode(); 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); + sl.complete(code, info, r_options, calltip); get_text_editor()->set_code_hint(calltip); } void ShaderTextEditor::_validate_script() { - _check_shader_mode(); + emit_signal(SNAME("script_changed")); // Ensure to notify that it changed, so it is applied - String code = get_text_editor()->get_text(); - //List<StringName> params; - //shader->get_param_list(¶ms); + String code; - 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; + if (shader.is_valid()) { + _check_shader_mode(); + code = shader->get_code(); + } else { + code = shader_inc->get_code(); + } - ShaderLanguage sl; + 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); - sl.enable_warning_checking(saved_warnings_enabled); - sl.set_warning_flags(saved_warning_flags); + 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)); + } + } - last_compile_result = sl.compile(code, info); + set_error(""); + set_error_count(0); if (last_compile_result != OK) { - String error_text = "error(" + itos(sl.get_error_line()) + "): " + sl.get_error_text(); + //preprocessor error + ERR_FAIL_COND(err_positions.size() == 0); + + String error_text = error_pp; + int error_line = err_positions.front()->get().line; + if (err_positions.size() == 1) { + // Error in main file + error_text = "error(" + itos(error_line) + "): " + error_text; + } else { + error_text = "error(" + itos(error_line) + ") in include " + err_positions.back()->get().file.get_file() + ":" + itos(err_positions.back()->get().line) + ": " + error_text; + set_error_count(err_positions.size() - 1); + } + set_error(error_text); - set_error_pos(sl.get_error_line() - 1, 0); + set_error_pos(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)); } - get_text_editor()->set_line_background_color(sl.get_error_line() - 1, marked_line_color); + get_text_editor()->set_line_background_color(error_line - 1, marked_line_color); + + set_warning_count(0); + } 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)); + 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); + } } - set_error(""); - } + sl.set_warning_flags(flags); - 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); + ShaderLanguage::ShaderCompileInfo info; + info.global_shader_uniform_type_func = _get_global_shader_uniform_type; + + if (shader.is_null()) { + info.is_include = true; + } else { + Shader::Mode mode = shader->get_mode(); + info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(mode)); + info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(mode)); + info.shader_types = ShaderTypes::get_singleton()->get_types(); + } + + code = code_pp; + //compiler error + last_compile_result = sl.compile(code, info); + + if (last_compile_result != OK) { + String error_text; + int error_line; + Vector<ShaderLanguage::FilePosition> include_positions = sl.get_include_positions(); + if (include_positions.size() > 1) { + //error is in an include + error_line = include_positions[0].line; + error_text = "error(" + itos(error_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 { + error_line = sl.get_error_line(); + error_text = "error(" + itos(error_line) + "): " + sl.get_error_text(); + set_error_count(0); + } + set_error(error_text); + set_error_pos(error_line - 1, 0); + get_text_editor()->set_line_background_color(error_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_changed")); + + emit_signal(SNAME("script_validated"), last_compile_result == OK); // Notify that validation finished, to update the list of scripts } void ShaderTextEditor::_update_warning_panel() { @@ -334,6 +609,7 @@ void ShaderTextEditor::_update_warning_panel() { } void ShaderTextEditor::_bind_methods() { + ADD_SIGNAL(MethodInfo("script_validated", PropertyInfo(Variant::BOOL, "valid"))); } ShaderTextEditor::ShaderTextEditor() { @@ -469,6 +745,8 @@ void ShaderEditor::_warning_clicked(Variant p_line) { void ShaderEditor::_bind_methods() { ClassDB::bind_method("_show_warnings_panel", &ShaderEditor::_show_warnings_panel); ClassDB::bind_method("_warning_clicked", &ShaderEditor::_warning_clicked); + + ADD_SIGNAL(MethodInfo("validation_changed")); } void ShaderEditor::ensure_select_current() { @@ -520,15 +798,23 @@ void ShaderEditor::_update_warnings(bool p_validate) { } void ShaderEditor::_check_for_external_edit() { - if (shader.is_null() || !shader.is_valid()) { + 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_built_in()) { + if (shader.is_null() || shader->is_built_in()) { return; } - bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change")); if (shader->get_last_modified_time() != FileAccess::get_modified_time(shader->get_path())) { if (use_autoreload) { _reload_shader_from_disk(); @@ -542,11 +828,32 @@ 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_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 ShaderEditor::_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 ShaderEditor::_reload() { + if (shader.is_valid()) { + _reload_shader_from_disk(); + } else if (shader_inc.is_valid()) { + _reload_shader_include_from_disk(); + } +} + void ShaderEditor::edit(const Ref<Shader> &p_shader) { if (p_shader.is_null() || !p_shader->is_text_shader()) { return; @@ -557,37 +864,84 @@ void ShaderEditor::edit(const Ref<Shader> &p_shader) { } shader = p_shader; + shader_inc = Ref<ShaderInclude>(); + + shader_editor->set_edited_shader(shader); +} + +void ShaderEditor::edit(const Ref<ShaderInclude> &p_shader_inc) { + if (p_shader_inc.is_null()) { + return; + } - shader_editor->set_edited_shader(p_shader); + if (shader_inc == p_shader_inc) { + return; + } - //vertex_editor->set_edited_shader(shader,ShaderLanguage::SHADER_MATERIAL_VERTEX); - // see if already has it + shader_inc = p_shader_inc; + shader = Ref<Shader>(); + + shader_editor->set_edited_shader_include(p_shader_inc); } void ShaderEditor::save_external_data(const String &p_str) { - if (shader.is_null()) { + if (shader.is_null() && shader_inc.is_null()) { disk_changed->hide(); return; } apply_shaders(); - if (!shader->is_built_in()) { - //external shader, save it - ResourceSaver::save(shader->get_path(), shader); + + 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 ShaderEditor::validate_script() { + shader_editor->_validate_script(); +} + +bool ShaderEditor::is_unsaved() const { + return shader_editor->get_text_editor()->get_saved_version() != shader_editor->get_text_editor()->get_version(); +} + void ShaderEditor::apply_shaders() { + String editor_code = shader_editor->get_text_editor()->get_text(); 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) { + 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 ShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { @@ -640,7 +994,7 @@ void ShaderEditor::_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 = shader_editor->get_text_editor()->get_bookmarked_lines(); + PackedInt32Array bookmark_list = shader_editor->get_text_editor()->get_bookmarked_lines(); if (bookmark_list.size() == 0) { return; } @@ -700,9 +1054,12 @@ ShaderEditor::ShaderEditor() { _update_warnings(false); shader_editor = memnew(ShaderTextEditor); + + shader_editor->connect("script_validated", callable_mp(this, &ShaderEditor::_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_WIDE); + shader_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); shader_editor->connect("show_warnings_panel", callable_mp(this, &ShaderEditor::_show_warnings_panel)); shader_editor->connect("script_changed", callable_mp(this, &ShaderEditor::apply_shaders)); @@ -793,7 +1150,7 @@ ShaderEditor::ShaderEditor() { VSplitContainer *editor_box = memnew(VSplitContainer); main_container->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(shader_editor); @@ -825,8 +1182,8 @@ ShaderEditor::ShaderEditor() { 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->connect("confirmed", callable_mp(this, &ShaderEditor::_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, &ShaderEditor::save_external_data)); @@ -836,50 +1193,317 @@ ShaderEditor::ShaderEditor() { _editor_settings_changed(); } +void ShaderEditorPlugin::_update_shader_list() { + shader_list->clear(); + for (uint32_t i = 0; i < edited_shaders.size(); i++) { + Ref<Resource> shader = edited_shaders[i].shader; + if (shader.is_null()) { + shader = edited_shaders[i].shader_inc; + } + + 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)); + } + } + + bool unsaved = false; + if (edited_shaders[i].shader_editor) { + unsaved = edited_shaders[i].shader_editor->is_unsaved(); + } + // TODO: Handle visual shaders too. + + if (unsaved) { + text += "(*)"; + } + + 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")); + + shader_list->add_item(text, icon); + shader_list->set_item_tooltip(shader_list->get_item_count() - 1, path); + } + + if (shader_tabs->get_tab_count()) { + shader_list->select(shader_tabs->get_current_tab()); + } + + for (int i = 1; i < FILE_MAX; i++) { + file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), edited_shaders.size() == 0); + } + + _update_shader_list_status(); +} + +void ShaderEditorPlugin::_update_shader_list_status() { + for (int i = 0; i < shader_list->get_item_count(); i++) { + ShaderEditor *se = Object::cast_to<ShaderEditor>(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"))); + } + } + } +} + void ShaderEditorPlugin::edit(Object *p_object) { - Shader *s = Object::cast_to<Shader>(p_object); - shader_editor->edit(s); + EditedShader es; + + 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(ShaderEditor); + 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 { + 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(ShaderEditor); + 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)); + } + } + + shader_tabs->set_current_tab(shader_tabs->get_tab_count() - 1); + edited_shaders.push_back(es); + _update_shader_list(); } bool ShaderEditorPlugin::handles(Object *p_object) const { - Shader *shader = Object::cast_to<Shader>(p_object); - return shader != nullptr && shader->is_text_shader(); + return Object::cast_to<Shader>(p_object) != nullptr || Object::cast_to<ShaderInclude>(p_object) != nullptr; } void ShaderEditorPlugin::make_visible(bool p_visible) { if (p_visible) { - button->show(); - EditorNode::get_singleton()->make_bottom_panel_item_visible(shader_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(main_split); + } +} - } else { - button->hide(); - if (shader_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->hide_bottom_panel(); +void ShaderEditorPlugin::selected_notify() { +} + +ShaderEditor *ShaderEditorPlugin::get_shader_editor(const Ref<Shader> &p_for_shader) { + for (uint32_t i = 0; i < edited_shaders.size(); i++) { + if (edited_shaders[i].shader == p_for_shader) { + return edited_shaders[i].shader_editor; } - shader_editor->apply_shaders(); } + return nullptr; } -void ShaderEditorPlugin::selected_notify() { - shader_editor->ensure_select_current(); +VisualShaderEditor *ShaderEditorPlugin::get_visual_shader_editor(const Ref<Shader> &p_for_shader) { + for (uint32_t i = 0; i < edited_shaders.size(); i++) { + if (edited_shaders[i].shader == p_for_shader) { + return edited_shaders[i].visual_shader_editor; + } + } + return nullptr; } void ShaderEditorPlugin::save_external_data() { - shader_editor->save_external_data(); + for (uint32_t i = 0; i < edited_shaders.size(); i++) { + if (edited_shaders[i].shader_editor) { + edited_shaders[i].shader_editor->save_external_data(); + } + } + _update_shader_list(); } void ShaderEditorPlugin::apply_changes() { - shader_editor->apply_shaders(); + for (uint32_t i = 0; i < edited_shaders.size(); i++) { + if (edited_shaders[i].shader_editor) { + edited_shaders[i].shader_editor->apply_shaders(); + } + } +} + +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); +} + +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); + } +} + +void ShaderEditorPlugin::_close_shader(int p_index) { + int index = shader_tabs->get_current_tab(); + ERR_FAIL_INDEX(index, shader_tabs->get_tab_count()); + Control *c = shader_tabs->get_tab_control(index); + memdelete(c); + edited_shaders.remove_at(index); + _update_shader_list(); + EditorNode::get_singleton()->get_undo_redo()->clear_history(); // To prevent undo on deleted graphs. +} + +void ShaderEditorPlugin::_resource_saved(Object *obj) { + // May have been renamed on save. + for (uint32_t i = 0; i < edited_shaders.size(); i++) { + if (edited_shaders[i].shader.ptr() == obj) { + _update_shader_list(); + return; + } + } +} + +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.plus_file("new_shader"), false, false, 0); + shader_create_dialog->popup_centered(); + } break; + case FILE_NEW_INCLUDE: { + String base_path = FileSystemDock::get_singleton()->get_current_path().get_base_dir(); + shader_create_dialog->config(base_path.plus_file("new_shader"), false, false, 2); + shader_create_dialog->popup_centered(); + } break; + case FILE_OPEN: { + InspectorDock::get_singleton()->open_resource("Shader"); + } break; + case FILE_OPEN_INCLUDE: { + InspectorDock::get_singleton()->open_resource("ShaderInclude"); + } break; + 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); + } + } break; + 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); + } + } break; + 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 FILE_CLOSE: { + _close_shader(shader_tabs->get_current_tab()); + } break; + } +} + +void ShaderEditorPlugin::_shader_created(Ref<Shader> p_shader) { + EditorNode::get_singleton()->push_item(p_shader.ptr()); +} + +void ShaderEditorPlugin::_shader_include_created(Ref<ShaderInclude> p_shader_inc) { + EditorNode::get_singleton()->push_item(p_shader_inc.ptr()); } ShaderEditorPlugin::ShaderEditorPlugin() { - shader_editor = memnew(ShaderEditor); + 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 = 2; 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)); + + 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); - shader_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE); - button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Shader"), shader_editor); - button->hide(); + // 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); - _2d = false; + 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 067711c75c..0980cc4db2 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -40,8 +40,29 @@ #include "scene/gui/text_edit.h" #include "scene/main/timer.h" #include "scene/resources/shader.h" +#include "scene/resources/shader_include.h" #include "servers/rendering/shader_warnings.h" +class ItemList; +class VisualShaderEditor; +class HSplitContainer; +class ShaderCreateDialog; + +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); @@ -51,15 +72,21 @@ class ShaderTextEditor : public CodeTextEditor { _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; + 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(); @@ -68,13 +95,23 @@ protected: 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(); }; @@ -114,45 +151,58 @@ class ShaderEditor : public PanelContainer { MenuButton *help_menu = nullptr; PopupMenu *context_menu = nullptr; RichTextLabel *warnings_panel = nullptr; - uint64_t idle; + 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> &ev); + 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; virtual Size2 get_minimum_size() const override { return Size2(0, 200); } - void save_external_data(const String &p_str = ""); ShaderEditor(); }; @@ -160,19 +210,55 @@ public: class ShaderEditorPlugin : public EditorPlugin { GDCLASS(ShaderEditorPlugin, EditorPlugin); - bool _2d; - ShaderEditor *shader_editor = nullptr; + struct EditedShader { + Ref<Shader> shader; + Ref<ShaderInclude> shader_inc; + ShaderEditor *shader_editor = nullptr; + VisualShaderEditor *visual_shader_editor = nullptr; + }; + + LocalVector<EditedShader> edited_shaders; + + enum { + FILE_NEW, + FILE_NEW_INCLUDE, + FILE_OPEN, + FILE_OPEN_INCLUDE, + FILE_SAVE, + FILE_SAVE_AS, + FILE_INSPECT, + FILE_CLOSE, + FILE_MAX + }; + + HSplitContainer *main_split = nullptr; + ItemList *shader_list = nullptr; + TabContainer *shader_tabs = nullptr; + Button *button = nullptr; + MenuButton *file_menu = nullptr; + + ShaderCreateDialog *shader_create_dialog = nullptr; + + 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); + + void _shader_created(Ref<Shader> p_shader); + void _shader_include_created(Ref<ShaderInclude> p_shader_inc); + void _update_shader_list_status(); 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; } + ShaderEditor *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; @@ -181,4 +267,4 @@ public: ~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 4458555de2..4874944d33 100644 --- a/editor/plugins/shader_file_editor_plugin.cpp +++ b/editor/plugins/shader_file_editor_plugin.cpp @@ -37,7 +37,6 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" -#include "editor/property_editor.h" #include "servers/display_server.h" #include "servers/rendering/shader_types.h" @@ -282,7 +281,7 @@ ShaderFileEditor::ShaderFileEditor() { 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); diff --git a/editor/plugins/skeleton_2d_editor_plugin.cpp b/editor/plugins/skeleton_2d_editor_plugin.cpp index 5a1505c232..3dc068a72a 100644 --- a/editor/plugins/skeleton_2d_editor_plugin.cpp +++ b/editor/plugins/skeleton_2d_editor_plugin.cpp @@ -32,6 +32,7 @@ #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 "thirdparty/misc/clipper.hpp" @@ -59,7 +60,7 @@ void Skeleton2DEditor::_menu_option(int p_option) { err_dialog->popup_centered(); return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -75,7 +76,7 @@ void Skeleton2DEditor::_menu_option(int p_option) { err_dialog->popup_centered(); return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 1b703a097c..c25f2bb25c 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -31,10 +31,10 @@ #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_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" @@ -42,6 +42,7 @@ #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/physics_body_3d.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" @@ -156,7 +157,7 @@ void BoneTransformEditor::_property_keyed(const String &p_path, bool p_advance) 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)); @@ -220,7 +221,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); }; @@ -230,12 +231,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: { @@ -250,10 +251,14 @@ 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; } @@ -262,31 +267,25 @@ void Skeleton3DEditor::init_pose(const bool p_all_bones) { return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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()); 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(); } @@ -314,7 +313,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)); @@ -335,7 +334,7 @@ void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) { return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Set Bone Rest"), UndoRedo::MERGE_ENDS); if (p_all_bones) { for (int i = 0; i < bone_len; i++) { @@ -355,9 +354,9 @@ void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) { } void Skeleton3DEditor::create_physical_skeleton() { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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(); @@ -368,37 +367,47 @@ void Skeleton3DEditor::create_physical_skeleton() { Vector<BoneInfo> bones_infos; bones_infos.resize(bc); - for (int bone_id = 0; bc > 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); + if (bc > 0) { + ur->create_action(TTR("Create physical bones"), UndoRedo::MERGE_ALL); + for (int bone_id = 0; bc > bone_id; ++bone_id) { + const int parent = skeleton->get_bone_parent(bone_id); - bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id); - - // 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"), UndoRedo::MERGE_ALL); - 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(); + 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); + + bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id); + + // Create physical bone on parent. + if (!bones_infos[parent].physical_bone) { + 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(), "_request_gizmo", physical_bone); + ur->add_do_method(Node3DEditor::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(); } } @@ -414,6 +423,7 @@ 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)); @@ -440,6 +450,73 @@ PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_chi return physical_bone; } +void Skeleton3DEditor::export_skeleton_profile() { + 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; + + int len = skeleton->get_bone_count(); + sp->set_bone_size(len); + for (int i = 0; i < len; 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 < len; 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(); @@ -510,7 +587,7 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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)) { @@ -558,7 +635,7 @@ void Skeleton3DEditor::_joint_tree_selection_changed() { } // 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() { @@ -577,7 +654,7 @@ void Skeleton3DEditor::update_joint_tree() { TreeItem *root = joint_tree->create_item(); - Map<int, TreeItem *> items; + HashMap<int, TreeItem *> items; items.insert(-1, root); @@ -589,7 +666,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); @@ -620,6 +697,11 @@ void Skeleton3DEditor::create_editors() { 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); @@ -633,11 +715,12 @@ void Skeleton3DEditor::create_editors() { // 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/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); @@ -693,7 +776,7 @@ void Skeleton3DEditor::create_editors() { 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->connect("pressed", callable_mp(this, &Skeleton3DEditor::insert_keys).bind(false)); key_insert_button->set_tooltip(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); @@ -701,7 +784,7 @@ void Skeleton3DEditor::create_editors() { 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->connect("pressed", callable_mp(this, &Skeleton3DEditor::insert_keys).bind(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)); animation_hb->add_child(key_insert_all_button); @@ -747,7 +830,7 @@ void Skeleton3DEditor::_notification(int p_what) { 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); + get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Object::CONNECT_ONESHOT); break; } case NOTIFICATION_ENTER_TREE: { @@ -755,7 +838,7 @@ void Skeleton3DEditor::_notification(int p_what) { 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)); @@ -810,7 +893,7 @@ Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, Skel 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) { @@ -832,8 +915,8 @@ void fragment() { )"); handle_material->set_shader(handle_shader); Ref<Texture2D> handle = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("EditorBoneHandle"), SNAME("EditorIcons")); - handle_material->set_shader_param("point_size", handle->get_width()); - handle_material->set_shader_param("texture_albedo", handle); + handle_material->set_shader_uniform("point_size", handle->get_width()); + handle_material->set_shader_uniform("texture_albedo", handle); handles_mesh_instance = memnew(MeshInstance3D); handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); @@ -1226,7 +1309,7 @@ void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, c Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); Node3DEditor *ne = Node3DEditor::get_singleton(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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++) { diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index 911e39a34f..9747ed8374 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -31,6 +31,7 @@ #ifndef SKELETON_3D_EDITOR_PLUGIN_H #define SKELETON_3D_EDITOR_PLUGIN_H +#include "editor/editor_file_dialog.h" #include "editor/editor_plugin.h" #include "editor/editor_properties.h" #include "node_3d_editor_plugin.h" @@ -40,6 +41,7 @@ #include "scene/resources/immediate_mesh.h" class EditorInspectorPluginSkeleton; +class EditorUndoRedoManager; class Joint; class PhysicalBone3D; class Skeleton3DEditorPlugin; @@ -63,7 +65,7 @@ class BoneTransformEditor : public VBoxContainer { Skeleton3D *skeleton = nullptr; // String property; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; bool toggle_enabled = false; bool updating = false; @@ -96,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 { @@ -131,7 +134,7 @@ class Skeleton3DEditor : public VBoxContainer { EditorFileDialog *file_dialog = nullptr; - bool keyable; + bool keyable = false; static Skeleton3DEditor *singleton; @@ -147,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); @@ -155,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); @@ -181,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(); diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index 6a63875324..e45c907e86 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -34,6 +34,7 @@ #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" @@ -122,7 +123,7 @@ 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 MeshInstance2D")); + debug_uv_dialog->set_ok_button_text(TTR("Create MeshInstance2D")); debug_uv_dialog->set_title(TTR("MeshInstance2D Preview")); _update_mesh_data(); @@ -131,7 +132,7 @@ void Sprite2DEditor::_menu_option(int p_option) { } 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(); @@ -139,7 +140,7 @@ void Sprite2DEditor::_menu_option(int p_option) { debug_uv->update(); } 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(); @@ -148,7 +149,7 @@ void Sprite2DEditor::_menu_option(int p_option) { } 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(); @@ -160,6 +161,11 @@ void Sprite2DEditor::_menu_option(int p_option) { } 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!")); @@ -337,7 +343,7 @@ 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -395,7 +401,7 @@ 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Convert to Polygon2D")); ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", node, polygon_2d_instance, true, false); ur->add_do_reference(polygon_2d_instance); @@ -417,7 +423,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -450,7 +456,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -546,7 +552,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); @@ -554,7 +560,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); @@ -562,7 +568,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); diff --git a/editor/plugins/sprite_2d_editor_plugin.h b/editor/plugins/sprite_2d_editor_plugin.h index 8e3dc19c7e..b87f108bd2 100644 --- a/editor/plugins/sprite_2d_editor_plugin.h +++ b/editor/plugins/sprite_2d_editor_plugin.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SPRITE_EDITOR_PLUGIN_H -#define SPRITE_EDITOR_PLUGIN_H +#ifndef SPRITE_2D_EDITOR_PLUGIN_H +#define SPRITE_2D_EDITOR_PLUGIN_H #include "editor/editor_plugin.h" #include "scene/2d/sprite_2d.h" @@ -111,4 +111,4 @@ public: ~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 29beb8be84..205fed48b4 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -37,6 +37,7 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/scene_tree_dock.h" #include "scene/3d/sprite_3d.h" #include "scene/gui/center_container.h" @@ -119,14 +120,14 @@ void SpriteFramesEditor::_sheet_preview_draw() { 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("accent_color", "Editor"); - for (Set<int>::Element *E = frames_selected.front(); E; E = E->next()) { - const int idx = E->get(); + 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); @@ -140,7 +141,7 @@ void SpriteFramesEditor::_sheet_preview_draw() { } 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) { @@ -248,8 +249,8 @@ void SpriteFramesEditor::_sheet_add_frames() { int fc = frames->get_frame_count(edited_anim); - for (Set<int>::Element *E = frames_selected.front(); E; E = E->next()) { - int idx = E->get(); + for (const int &E : frames_selected) { + int idx = E; const Point2 frame_coords(idx % frame_count.x, idx / frame_count.x); Ref<AtlasTexture> at; @@ -423,6 +424,7 @@ void SpriteFramesEditor::_notification(int p_what) { 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"))); + 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"))); @@ -449,7 +451,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 } @@ -516,7 +518,7 @@ void SpriteFramesEditor::_paste_pressed() { 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 } @@ -750,7 +752,7 @@ void SpriteFramesEditor::_animation_name_edited() { undo_redo->add_do_method(this, "_update_library"); undo_redo->add_undo_method(this, "_update_library"); - edited_anim = new_name; + edited_anim = name; undo_redo->commit_action(); } @@ -816,6 +818,10 @@ void SpriteFramesEditor::_animation_remove_confirmed() { undo_redo->commit_action(); } +void SpriteFramesEditor::_animation_search_text_changed(const String &p_text) { + _update_library(); +} + void SpriteFramesEditor::_animation_loop_changed() { if (updating) { return; @@ -900,14 +906,19 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) { 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); @@ -970,7 +981,6 @@ 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) { @@ -1001,6 +1011,10 @@ void SpriteFramesEditor::edit(SpriteFrames *p_frames) { } } +void SpriteFramesEditor::set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo) { + undo_redo = p_undo_redo; +} + Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { if (!frames->has_animation(edited_anim)) { return false; @@ -1012,7 +1026,7 @@ Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_f return Variant(); } - RES frame = frames->get_frame(edited_anim, idx); + Ref<Resource> frame = frames->get_frame(edited_anim, idx); if (frame.is_null()) { return Variant(); @@ -1036,7 +1050,7 @@ bool SpriteFramesEditor::can_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"]; Ref<Texture2D> texture = r; @@ -1080,7 +1094,7 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da int at_pos = tree->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; @@ -1157,6 +1171,13 @@ SpriteFramesEditor::SpriteFramesEditor() { hbc_animlist->add_child(remove_anim); remove_anim->connect("pressed", callable_mp(this, &SpriteFramesEditor::_animation_remove)); + anim_search_box = memnew(LineEdit); + hbc_animlist->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); @@ -1290,7 +1311,7 @@ SpriteFramesEditor::SpriteFramesEditor() { empty2->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)); + file->connect("files_selected", callable_mp(this, &SpriteFramesEditor::_file_load_request).bind(-1)); loading_scene = false; sel = -1; @@ -1317,7 +1338,7 @@ SpriteFramesEditor::SpriteFramesEditor() { 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), varray(PARAM_FRAME_COUNT)); + split_sheet_h->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT)); split_sheet_hb->add_child(memnew(Label(TTR("Vertical:")))); split_sheet_v = memnew(SpinBox); @@ -1325,7 +1346,7 @@ SpriteFramesEditor::SpriteFramesEditor() { 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), varray(PARAM_FRAME_COUNT)); + 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:")))); @@ -1333,13 +1354,13 @@ SpriteFramesEditor::SpriteFramesEditor() { 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), varray(PARAM_SIZE)); + 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), varray(PARAM_SIZE)); + 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)); @@ -1348,13 +1369,13 @@ SpriteFramesEditor::SpriteFramesEditor() { 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), varray(PARAM_USE_CURRENT)); + 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), varray(PARAM_USE_CURRENT)); + 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)); @@ -1363,13 +1384,13 @@ SpriteFramesEditor::SpriteFramesEditor() { 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), varray(PARAM_USE_CURRENT)); + 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), varray(PARAM_USE_CURRENT)); + 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(); @@ -1448,10 +1469,14 @@ 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 shrinked if desired. + set_split_offset(56 * EDSCALE); } void SpriteFramesEditorPlugin::edit(Object *p_object) { - frames_editor->set_undo_redo(&get_undo_redo()); + frames_editor->set_undo_redo(get_undo_redo()); SpriteFrames *s; AnimatedSprite2D *animated_sprite = Object::cast_to<AnimatedSprite2D>(p_object); diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index d31ce84d09..f2530b732f 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -37,6 +37,7 @@ #include "scene/gui/check_button.h" #include "scene/gui/dialogs.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" @@ -44,13 +45,14 @@ #include "scene/gui/tree.h" class EditorFileDialog; +class EditorUndoRedoManager; class SpriteFramesEditor : public HSplitContainer { GDCLASS(SpriteFramesEditor, HSplitContainer); enum { PARAM_USE_CURRENT, // Used in callbacks to indicate `dominant_param` should be not updated. - PARAM_FRAME_COUNT, // Keep "Horizontal" & "Vertial" values. + PARAM_FRAME_COUNT, // Keep "Horizontal" & "Vertical" values. PARAM_SIZE, // Keep "Size" values. }; int dominant_param = PARAM_FRAME_COUNT; @@ -73,6 +75,7 @@ class SpriteFramesEditor : public HSplitContainer { Button *new_anim = nullptr; Button *remove_anim = nullptr; + LineEdit *anim_search_box = nullptr; Tree *animations = nullptr; SpinBox *anim_speed = nullptr; @@ -103,9 +106,9 @@ class SpriteFramesEditor : public HSplitContainer { Button *split_sheet_zoom_reset = nullptr; Button *split_sheet_zoom_in = nullptr; EditorFileDialog *file_split_sheet = nullptr; - Set<int> frames_selected; - Set<int> frames_toggled_by_mouse_hover; - int last_frame_selected; + HashSet<int> frames_selected; + HashSet<int> frames_toggled_by_mouse_hover; + int last_frame_selected = 0; float scale_ratio; int thumbnail_default_size; @@ -137,6 +140,7 @@ class SpriteFramesEditor : public HSplitContainer { 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); @@ -148,7 +152,7 @@ class SpriteFramesEditor : public HSplitContainer { bool updating; bool updating_split_settings = false; // Skip SpinBox/Range callback when setting value by code. - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> 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; @@ -173,7 +177,7 @@ protected: static void _bind_methods(); public: - void set_undo_redo(UndoRedo *p_undo_redo) { undo_redo = p_undo_redo; } + void set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo); void edit(SpriteFrames *p_frames); SpriteFramesEditor(); diff --git a/editor/plugins/style_box_editor_plugin.cpp b/editor/plugins/style_box_editor_plugin.cpp index a3cbaf527e..d4baff34e2 100644 --- a/editor/plugins/style_box_editor_plugin.cpp +++ b/editor/plugins/style_box_editor_plugin.cpp @@ -32,6 +32,13 @@ #include "editor/editor_scale.h" +bool StyleBoxPreview::grid_preview_enabled = true; + +void StyleBoxPreview::_grid_preview_toggled(bool p_active) { + grid_preview_enabled = p_active; + preview->update(); +} + bool EditorInspectorPluginStyleBox::can_handle(Object *p_object) { return Object::cast_to<StyleBox>(p_object) != nullptr; } @@ -53,6 +60,8 @@ 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(); } @@ -60,9 +69,31 @@ void StyleBoxPreview::_sb_changed() { preview->update(); } +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_normal_texture(get_theme_icon(SNAME("StyleBoxGridInvisible"), SNAME("EditorIcons"))); + grid_preview->set_pressed_texture(get_theme_icon(SNAME("StyleBoxGridVisible"), SNAME("EditorIcons"))); + grid_preview->set_hover_texture(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 +101,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,11 +123,23 @@ 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() { diff --git a/editor/plugins/style_box_editor_plugin.h b/editor/plugins/style_box_editor_plugin.h index 663440ae31..a072745d8f 100644 --- a/editor/plugins/style_box_editor_plugin.h +++ b/editor/plugins/style_box_editor_plugin.h @@ -40,11 +40,16 @@ class StyleBoxPreview : public VBoxContainer { GDCLASS(StyleBoxPreview, VBoxContainer); + 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(); diff --git a/editor/plugins/text_control_editor_plugin.cpp b/editor/plugins/text_control_editor_plugin.cpp deleted file mode 100644 index 4290888e94..0000000000 --- a/editor/plugins/text_control_editor_plugin.cpp +++ /dev/null @@ -1,660 +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_node.h" -#include "editor/editor_scale.h" -#include "editor/multi_node_edit.h" - -void TextControlEditor::_notification(int p_what) { - switch (p_what) { - 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; - } -} - -void TextControlEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_update_control"), &TextControlEditor::_update_control); -} - -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 if (font_list->get_selected() >= 0) { - 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_controls.is_empty()) { - String font_selected; - bool same_font = true; - String style_selected; - bool same_style = true; - int font_size = 0; - bool same_font_size = true; - int outline_size = 0; - bool same_outline_size = true; - Color font_color = Color{ 1.0f, 1.0f, 1.0f }; - bool same_font_color = true; - Color outline_color = Color{ 1.0f, 1.0f, 1.0f }; - bool same_outline_color = true; - - int count = edited_controls.size(); - for (int i = 0; i < count; ++i) { - Control *edited_control = edited_controls[i]; - - StringName edited_color; - StringName edited_font; - StringName edited_font_size; - - // Get override names. - if (Object::cast_to<RichTextLabel>(edited_control)) { - edited_color = SNAME("default_color"); - edited_font = SNAME("normal_font"); - edited_font_size = SNAME("normal_font_size"); - } else { - edited_color = SNAME("font_color"); - edited_font = SNAME("font"); - edited_font_size = SNAME("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) { - if (i > 0) { - same_font = same_font && (custom_font == font); - } - custom_font = font; - - font_selected = TTR("[Custom Font]"); - same_style = false; - } else { - 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)) { - if (i > 0) { - same_font = same_font && (name == font_selected); - same_style = same_style && (style == style_selected); - } - font_selected = name; - style_selected = style; - } else { - if (i > 0) { - same_font = same_font && (custom_font == font); - } - custom_font = font; - - font_selected = TTR("[Custom Font]"); - same_style = false; - } - } - } else { - if (i > 0) { - same_font = same_font && (font_selected == TTR("[Theme Default]")); - } - - font_selected = TTR("[Theme Default]"); - same_style = false; - } - - int current_font_size = edited_control->get_theme_font_size(edited_font_size); - int current_outline_size = edited_control->get_theme_constant(SNAME("outline_size")); - Color current_font_color = edited_control->get_theme_color(edited_color); - Color current_outline_color = edited_control->get_theme_color(SNAME("font_outline_color")); - if (i > 0) { - same_font_size = same_font_size && (font_size == current_font_size); - same_outline_size = same_outline_size && (outline_size == current_outline_size); - same_font_color = same_font_color && (font_color == current_font_color); - same_outline_color = same_outline_color && (outline_color == current_outline_color); - } - - font_size = current_font_size; - outline_size = current_outline_size; - font_color = current_font_color; - outline_color = current_outline_color; - } - _update_fonts_menu(); - if (same_font) { - for (int j = 0; j < font_list->get_item_count(); j++) { - if (font_list->get_item_text(j) == font_selected) { - font_list->select(j); - break; - } - } - } else { - custom_font = Ref<Font>(); - font_list->select(-1); - } - - _update_styles_menu(); - if (same_style) { - for (int j = 0; j < font_style_list->get_item_count(); j++) { - if (font_style_list->get_item_text(j) == style_selected) { - font_style_list->select(j); - break; - } - } - } else { - font_style_list->select(-1); - } - - // Get other theme overrides. - font_size_list->set_block_signals(true); - if (same_font_size) { - font_size_list->get_line_edit()->set_text(String::num_uint64(font_size)); - font_size_list->set_value(font_size); - } else { - font_size_list->get_line_edit()->set_text(""); - } - font_size_list->set_block_signals(false); - - outline_size_list->set_block_signals(true); - if (same_outline_size) { - outline_size_list->get_line_edit()->set_text(String::num_uint64(outline_size)); - outline_size_list->set_value(outline_size); - } else { - outline_size_list->get_line_edit()->set_text(""); - } - outline_size_list->set_block_signals(false); - - if (!same_font_color) { - font_color = Color{ 1.0f, 1.0f, 1.0f }; - } - font_color_picker->set_pick_color(font_color); - - if (!same_outline_color) { - outline_color = Color{ 1.0f, 1.0f, 1.0f }; - } - outline_color_picker->set_pick_color(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_controls.is_empty()) { - return; - } - - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Set Font")); - - int count = edited_controls.size(); - for (int i = 0; i < count; ++i) { - Control *edited_control = edited_controls[i]; - - StringName edited_font; - if (Object::cast_to<RichTextLabel>(edited_control)) { - edited_font = SNAME("normal_font"); - } else { - edited_font = SNAME("font"); - } - - if (font_list->get_selected_id() == FONT_INFO_THEME_DEFAULT) { - // Remove font override. - ur->add_do_method(edited_control, "remove_theme_font_override", edited_font); - } else if (font_list->get_selected_id() == FONT_INFO_USER_CUSTOM) { - // Restore "custom_font". - ur->add_do_method(edited_control, "add_theme_font_override", edited_font, custom_font); - } else if (font_list->get_selected() >= 0) { - // Load new font resource using selected name and style. - String name = font_list->get_item_text(font_list->get_selected()); - String style = font_style_list->get_item_text(font_style_list->get_selected()); - if (style.is_empty()) { - style = "Default"; - } - if (fonts.has(name)) { - Ref<FontData> fd = ResourceLoader::load(fonts[name][style]); - if (fd.is_valid()) { - Ref<Font> font; - font.instantiate(); - font->add_data(fd); - ur->add_do_method(edited_control, "add_theme_font_override", edited_font, font); - } - } - } - - if (edited_control->has_theme_font_override(edited_font)) { - ur->add_undo_method(edited_control, "add_theme_font_override", edited_font, edited_control->get_theme_font(edited_font)); - } else { - ur->add_undo_method(edited_control, "remove_theme_font_override", edited_font); - } - } - - ur->add_do_method(this, "_update_control"); - ur->add_undo_method(this, "_update_control"); - - ur->commit_action(); -} - -void TextControlEditor::_font_size_selected(double p_size) { - if (edited_controls.is_empty()) { - return; - } - - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Set Font Size")); - - int count = edited_controls.size(); - for (int i = 0; i < count; ++i) { - Control *edited_control = edited_controls[i]; - - StringName edited_font_size; - if (Object::cast_to<RichTextLabel>(edited_control)) { - edited_font_size = SNAME("normal_font_size"); - } else { - edited_font_size = SNAME("font_size"); - } - - ur->add_do_method(edited_control, "add_theme_font_size_override", edited_font_size, p_size); - if (edited_control->has_theme_font_size_override(edited_font_size)) { - ur->add_undo_method(edited_control, "add_theme_font_size_override", edited_font_size, edited_control->get_theme_font_size(edited_font_size)); - } else { - ur->add_undo_method(edited_control, "remove_theme_font_size_override", edited_font_size); - } - } - - ur->add_do_method(this, "_update_control"); - ur->add_undo_method(this, "_update_control"); - - ur->commit_action(); -} - -void TextControlEditor::_outline_size_selected(double p_size) { - if (edited_controls.is_empty()) { - return; - } - - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Set Font Outline Size")); - - int count = edited_controls.size(); - for (int i = 0; i < count; ++i) { - Control *edited_control = edited_controls[i]; - - ur->add_do_method(edited_control, "add_theme_constant_override", "outline_size", p_size); - if (edited_control->has_theme_constant_override("outline_size")) { - ur->add_undo_method(edited_control, "add_theme_constant_override", "outline_size", edited_control->get_theme_constant(SNAME("outline_size"))); - } else { - ur->add_undo_method(edited_control, "remove_theme_constant_override", "outline_size"); - } - } - - ur->add_do_method(this, "_update_control"); - ur->add_undo_method(this, "_update_control"); - - ur->commit_action(); -} - -void TextControlEditor::_font_color_changed(const Color &p_color) { - if (edited_controls.is_empty()) { - return; - } - - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Set Font Color"), UndoRedo::MERGE_ENDS); - - int count = edited_controls.size(); - for (int i = 0; i < count; ++i) { - Control *edited_control = edited_controls[i]; - - StringName edited_color; - if (Object::cast_to<RichTextLabel>(edited_control)) { - edited_color = SNAME("default_color"); - } else { - edited_color = SNAME("font_color"); - } - - ur->add_do_method(edited_control, "add_theme_color_override", edited_color, p_color); - if (edited_control->has_theme_color_override(edited_color)) { - ur->add_undo_method(edited_control, "add_theme_color_override", edited_color, edited_control->get_theme_color(edited_color)); - } else { - ur->add_undo_method(edited_control, "remove_theme_color_override", edited_color); - } - } - - ur->add_do_method(this, "_update_control"); - ur->add_undo_method(this, "_update_control"); - - ur->commit_action(); -} - -void TextControlEditor::_outline_color_changed(const Color &p_color) { - if (edited_controls.is_empty()) { - return; - } - - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Set Font Outline Color"), UndoRedo::MERGE_ENDS); - - int count = edited_controls.size(); - for (int i = 0; i < count; ++i) { - Control *edited_control = edited_controls[i]; - - ur->add_do_method(edited_control, "add_theme_color_override", "font_outline_color", p_color); - if (edited_control->has_theme_color_override("font_outline_color")) { - ur->add_undo_method(edited_control, "add_theme_color_override", "font_outline_color", edited_control->get_theme_color(SNAME("font_outline_color"))); - } else { - ur->add_undo_method(edited_control, "remove_theme_color_override", "font_outline_color"); - } - } - - ur->add_do_method(this, "_update_control"); - ur->add_undo_method(this, "_update_control"); - - ur->commit_action(); -} - -void TextControlEditor::_clear_formatting() { - if (edited_controls.is_empty()) { - return; - } - - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Clear Control Formatting")); - - int count = edited_controls.size(); - for (int i = 0; i < count; ++i) { - Control *edited_control = edited_controls[i]; - - StringName edited_color; - StringName edited_font; - StringName edited_font_size; - - // Get override names. - if (Object::cast_to<RichTextLabel>(edited_control)) { - edited_color = SNAME("default_color"); - edited_font = SNAME("normal_font"); - edited_font_size = SNAME("normal_font_size"); - } else { - edited_color = SNAME("font_color"); - edited_font = SNAME("font"); - edited_font_size = SNAME("font_size"); - } - - ur->add_do_method(edited_control, "begin_bulk_theme_override"); - ur->add_undo_method(edited_control, "begin_bulk_theme_override"); - - ur->add_do_method(edited_control, "remove_theme_font_override", edited_font); - if (edited_control->has_theme_font_override(edited_font)) { - ur->add_undo_method(edited_control, "add_theme_font_override", edited_font, edited_control->get_theme_font(edited_font)); - } - - ur->add_do_method(edited_control, "remove_theme_font_size_override", edited_font_size); - if (edited_control->has_theme_font_size_override(edited_font_size)) { - ur->add_undo_method(edited_control, "add_theme_font_size_override", edited_font_size, edited_control->get_theme_font_size(edited_font_size)); - } - - ur->add_do_method(edited_control, "remove_theme_color_override", edited_color); - if (edited_control->has_theme_color_override(edited_color)) { - ur->add_undo_method(edited_control, "add_theme_color_override", edited_color, edited_control->get_theme_color(edited_color)); - } - - ur->add_do_method(edited_control, "remove_theme_color_override", "font_outline_color"); - if (edited_control->has_theme_color_override("font_outline_color")) { - ur->add_undo_method(edited_control, "add_theme_color_override", "font_outline_color", edited_control->get_theme_color(SNAME("font_outline_color"))); - } - - ur->add_do_method(edited_control, "remove_theme_constant_override", "outline_size"); - if (edited_control->has_theme_constant_override("outline_size")) { - ur->add_undo_method(edited_control, "add_theme_constant_override", "outline_size", edited_control->get_theme_constant(SNAME("outline_size"))); - } - - ur->add_do_method(edited_control, "end_bulk_theme_override"); - ur->add_undo_method(edited_control, "end_bulk_theme_override"); - } - - ur->add_do_method(this, "_update_control"); - ur->add_undo_method(this, "_update_control"); - - ur->commit_action(); -} - -void TextControlEditor::edit(Object *p_object) { - Control *ctrl = Object::cast_to<Control>(p_object); - MultiNodeEdit *multi_node = Object::cast_to<MultiNodeEdit>(p_object); - - edited_controls.clear(); - custom_font = Ref<Font>(); - if (ctrl) { - edited_controls.append(ctrl); - _update_control(); - } else if (multi_node && handles(multi_node)) { - int count = multi_node->get_node_count(); - Node *scene = EditorNode::get_singleton()->get_edited_scene(); - - for (int i = 0; i < count; ++i) { - Control *child = Object::cast_to<Control>(scene->get_node(multi_node->get_node(i))); - edited_controls.append(child); - } - _update_control(); - } -} - -bool TextControlEditor::handles(Object *p_object) const { - Control *ctrl = Object::cast_to<Control>(p_object); - MultiNodeEdit *multi_node = Object::cast_to<MultiNodeEdit>(p_object); - - if (!ctrl && !multi_node) { - return false; - } else if (ctrl) { - bool valid = false; - ctrl->get("text", &valid); - return valid; - } else { - bool valid = true; - int count = multi_node->get_node_count(); - Node *scene = EditorNode::get_singleton()->get_edited_scene(); - - for (int i = 0; i < count; ++i) { - bool temp_valid = false; - Control *child = Object::cast_to<Control>(scene->get_node(multi_node->get_node(i))); - if (child) { - child->get("text", &temp_valid); - } - valid = valid && temp_valid; - - if (!valid) { - break; - } - } - - 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() { - 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 a475f6eba3..0000000000 --- a/editor/plugins/text_control_editor_plugin.h +++ /dev/null @@ -1,115 +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_plugin.h" -#include "scene/gui/color_picker.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; - - Vector<Control *> edited_controls; - Ref<Font> custom_font; - -protected: - void _notification(int p_what); - 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 = nullptr; - -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(); -}; - -#endif // TEXT_CONTROL_EDITOR_PLUGIN_H diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 34f3ec73c0..0900415b04 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -32,6 +32,7 @@ #include "core/os/keyboard.h" #include "editor/editor_node.h" +#include "editor/editor_settings.h" void TextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { ERR_FAIL_COND(p_highlighter.is_null()); @@ -43,11 +44,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(); @@ -89,11 +90,11 @@ Ref<Texture2D> TextEditor::get_theme_icon() { return EditorNode::get_singleton()->get_object_icon(text_file.ptr(), ""); } -RES TextEditor::get_edited_resource() const { +Ref<Resource> TextEditor::get_edited_resource() const { return text_file; } -void TextEditor::set_edited_resource(const RES &p_res) { +void TextEditor::set_edited_resource(const Ref<Resource> &p_res) { ERR_FAIL_COND(text_file.is_valid()); ERR_FAIL_COND(p_res.is_null()); @@ -127,8 +128,8 @@ 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() { @@ -164,7 +165,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; } @@ -275,7 +276,7 @@ void TextEditor::update_settings() { 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.bind(argp, 1)); + code_editor->get_text_editor()->set_tooltip_request_func(p_toolip_callback.bindp(argp, 1)); } Control *TextEditor::get_edit_menu() { @@ -412,7 +413,7 @@ void TextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { code_editor->convert_case(p_case); } -static ScriptEditorBase *create_editor(const RES &p_resource) { +static ScriptEditorBase *create_editor(const Ref<Resource> &p_resource) { if (Object::cast_to<TextFile>(*p_resource)) { return memnew(TextEditor); } @@ -524,7 +525,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(); diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index d03385d79e..15f7c45653 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -92,7 +92,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,8 +109,8 @@ 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 Ref<Resource> get_edited_resource() const override; + virtual void set_edited_resource(const Ref<Resource> &p_res) override; virtual void enable_editor() override; virtual void reload_text() override; virtual void apply_code() override; @@ -118,7 +118,7 @@ public: virtual Variant get_edit_state() override; virtual void set_edit_state(const Variant &p_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; diff --git a/editor/plugins/texture_3d_editor_plugin.cpp b/editor/plugins/texture_3d_editor_plugin.cpp index 0fc7079a24..64cafa17f3 100644 --- a/editor/plugins/texture_3d_editor_plugin.cpp +++ b/editor/plugins/texture_3d_editor_plugin.cpp @@ -57,8 +57,8 @@ void Texture3DEditor::_texture_changed() { } 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_uniform("layer", (layer->get_value() + 0.5) / texture->get_depth()); + material->set_shader_uniform("tex", texture->get_rid()); String format = Image::get_format_name(texture->get_format()); diff --git a/editor/plugins/texture_3d_editor_plugin.h b/editor/plugins/texture_3d_editor_plugin.h index 2f7f6f83bb..357bdb0845 100644 --- a/editor/plugins/texture_3d_editor_plugin.h +++ b/editor/plugins/texture_3d_editor_plugin.h @@ -91,4 +91,4 @@ public: 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 15f03fd46d..be382759f5 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -59,7 +59,7 @@ 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)) { @@ -70,7 +70,49 @@ void TexturePreview::_update_metadata_label_text() { format = texture->get_class(); } - metadata_label->set_text(vformat(String::utf8("%s×%s %s"), itos(texture->get_width()), 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) { @@ -82,7 +124,7 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { texture_display = memnew(TextureRect); 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_ignore_texture_size(true); add_child(texture_display); @@ -95,13 +137,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); diff --git a/editor/plugins/texture_layered_editor_plugin.cpp b/editor/plugins/texture_layered_editor_plugin.cpp index cb146fd342..2c6f70463d 100644 --- a/editor/plugins/texture_layered_editor_plugin.cpp +++ b/editor/plugins/texture_layered_editor_plugin.cpp @@ -68,9 +68,9 @@ void TextureLayeredEditor::_texture_changed() { } 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_uniform("layer", layer->get_value()); + materials[2]->set_shader_uniform("layer", layer->get_value()); + materials[texture->get_layered_type()]->set_shader_uniform("tex", texture->get_rid()); Vector3 v(1, 1, 1); v.normalize(); @@ -79,10 +79,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_uniform("normal", v); + materials[1]->set_shader_uniform("rot", b); + materials[2]->set_shader_uniform("normal", v); + materials[2]->set_shader_uniform("rot", b); String format = Image::get_format_name(texture->get_format()); diff --git a/editor/plugins/texture_layered_editor_plugin.h b/editor/plugins/texture_layered_editor_plugin.h index 830916e954..f49aa83eb2 100644 --- a/editor/plugins/texture_layered_editor_plugin.h +++ b/editor/plugins/texture_layered_editor_plugin.h @@ -93,4 +93,4 @@ public: 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 3fa12233a8..772bae6544 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -35,8 +35,12 @@ #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" +#include "scene/gui/separator.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; @@ -78,10 +82,11 @@ 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_rect(Rect2(Point2(), base_tex->get_size()), Color(0.5, 0.5, 0.5, 0.5), false); edit_draw->draw_texture(base_tex, Point2()); RS::get_singleton()->canvas_item_add_set_transform(edit_draw->get_canvas_item(), Transform2D()); @@ -228,11 +233,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_deferredp(nullptr, 0); + request_center = false; + } + if (node_ninepatch || obj_styleBox.is_valid()) { float margins[4] = { 0 }; if (node_ninepatch) { @@ -267,7 +280,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { } 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; @@ -817,8 +830,8 @@ void TextureRegionEditor::_notification(int p_what) { 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: { @@ -921,12 +934,8 @@ void TextureRegionEditor::edit(Object *p_obj) { 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); - } + popup_centered_ratio(0.5); + request_center = true; } void TextureRegionEditor::_texture_changed() { @@ -983,6 +992,9 @@ Vector2 TextureRegionEditor::snap_point(Vector2 p_target) const { } 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; @@ -998,7 +1010,7 @@ TextureRegionEditor::TextureRegionEditor() { 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); @@ -1082,12 +1094,12 @@ TextureRegionEditor::TextureRegionEditor() { panner->set_callbacks(callable_mp(this, &TextureRegionEditor::_scroll_callback), 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(FOCUS_CLICK); + edit_draw->set_focus_mode(Control::FOCUS_CLICK); draw_zoom = 1.0; edit_draw->set_clip_contents(true); @@ -1125,88 +1137,39 @@ TextureRegionEditor::TextureRegionEditor() { 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()) { - EditorNode::get_singleton()->make_bottom_panel_item_visible(region_editor); - } - } else { - if (region_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->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 uint32_t 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() { - manually_hidden = false; - - region_editor = memnew(TextureRegionEditor); - 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 = EditorNode::get_singleton()->add_bottom_panel_item(TTR("TextureRegion"), region_editor); - texture_region_button->hide(); + 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 1e1cc2b7b2..e3bbaf49fc 100644 --- a/editor/plugins/texture_region_editor_plugin.h +++ b/editor/plugins/texture_region_editor_plugin.h @@ -40,9 +40,10 @@ #include "scene/resources/texture.h" class ViewPanner; +class EditorUndoRedoManager; -class TextureRegionEditor : public VBoxContainer { - GDCLASS(TextureRegionEditor, VBoxContainer); +class TextureRegionEditor : public AcceptDialog { + GDCLASS(TextureRegionEditor, AcceptDialog); enum SnapMode { SNAP_NONE, @@ -68,13 +69,13 @@ class TextureRegionEditor : public VBoxContainer { VScrollBar *vscroll = nullptr; HScrollBar *hscroll = nullptr; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; 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; @@ -87,16 +88,17 @@ class TextureRegionEditor : public VBoxContainer { 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 _scroll_callback(Vector2 p_scroll_vec, bool p_alt); @@ -142,26 +144,27 @@ public: TextureRegionEditor(); }; -class TextureRegionEditorPlugin : public EditorPlugin { - GDCLASS(TextureRegionEditorPlugin, EditorPlugin); +// - bool manually_hidden; - Button *texture_region_button = nullptr; - TextureRegionEditor *region_editor = nullptr; +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 uint32_t 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(); }; diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index e17bea0f2a..af3959d47c 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -35,7 +35,9 @@ #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" void ThemeItemImportTree::_update_items_tree() { import_items_tree->clear(); @@ -70,9 +72,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); @@ -214,7 +224,7 @@ void ThemeItemImportTree::_update_items_tree() { 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); @@ -228,7 +238,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); @@ -242,7 +252,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); @@ -256,7 +266,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); @@ -270,7 +280,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); @@ -286,7 +296,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); @@ -787,7 +797,7 @@ void ThemeItemImportTree::_import_selected() { ProgressDialog::get_singleton()->end_task("import_theme_items"); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Import Theme Items")); ur->add_do_method(*edited_theme, "clear"); @@ -834,6 +844,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"))); @@ -881,15 +893,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); @@ -1098,15 +1105,15 @@ ThemeItemImportTree::ThemeItemImportTree() { select_all_items_button->set_flat(true); select_all_items_button->set_tooltip(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); 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); 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(); @@ -1122,7 +1129,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); } @@ -1137,12 +1144,12 @@ ThemeItemImportTree::ThemeItemImportTree() { import_collapse_types_button->set_flat(true); import_collapse_types_button->set_tooltip(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_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)); @@ -1173,6 +1180,8 @@ 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?")); @@ -1287,7 +1296,11 @@ void ThemeItemEditorDialog::_edited_type_selected() { _update_edit_item_tree(selected_type); } -void ThemeItemEditorDialog::_edited_type_button_pressed(Object *p_item, int p_column, int p_id) { +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; @@ -1461,7 +1474,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; @@ -1478,7 +1495,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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)); @@ -1497,7 +1514,7 @@ 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(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Add Theme Type")); ur->add_do_method(*edited_theme, "add_type", new_type); @@ -1509,7 +1526,7 @@ void ThemeItemEditorDialog::_add_theme_type(const String &p_new_text) { } 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Create Theme Item")); switch (p_data_type) { @@ -1554,7 +1571,7 @@ void ThemeItemEditorDialog::_remove_theme_type(const String &p_theme_type) { Ref<Theme> old_snapshot = edited_theme->duplicate(); Ref<Theme> new_snapshot = edited_theme->duplicate(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Remove Theme Type")); new_snapshot->remove_type(p_theme_type); @@ -1577,7 +1594,7 @@ void ThemeItemEditorDialog::_remove_data_type_items(Theme::DataType p_data_type, Ref<Theme> old_snapshot = edited_theme->duplicate(); Ref<Theme> new_snapshot = edited_theme->duplicate(); - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Remove Data Type Items From Theme")); new_snapshot->get_theme_item_list(p_data_type, p_item_type, &names); @@ -1606,7 +1623,7 @@ 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Remove Class Items From Theme")); for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) { @@ -1642,7 +1659,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Remove Custom Items From Theme")); for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) { @@ -1678,7 +1695,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Remove All Items From Theme")); for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) { @@ -1782,7 +1799,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -1862,10 +1879,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; } } @@ -1881,13 +1897,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); add_child(tc); + tc->set_theme_type_variation("TabContainerOdd"); // Edit Items tab. HSplitContainer *edit_dialog_hs = memnew(HSplitContainer); @@ -1909,7 +1926,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_edito 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_pressed", callable_mp(this, &ThemeItemEditorDialog::_edited_type_button_pressed)); + 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:")); @@ -1921,10 +1938,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); @@ -1942,42 +1958,42 @@ ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_edito 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_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_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_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_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_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)); @@ -2011,14 +2027,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); @@ -2077,7 +2093,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(vformat("*.%s; %s", E, TTR("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)); @@ -2096,6 +2112,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(); @@ -2234,6 +2252,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); @@ -2253,11 +2273,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; } @@ -2339,8 +2359,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) { @@ -2367,12 +2387,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]; } @@ -2400,7 +2420,7 @@ 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); @@ -2408,21 +2428,21 @@ HBoxContainer *ThemeTypeEditor::_create_property_control(Theme::DataType p_data_ item_rename_button->set_tooltip(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_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_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); @@ -2430,7 +2450,7 @@ HBoxContainer *ThemeTypeEditor::_create_property_control(Theme::DataType p_data_ item_rename_cancel_button->set_tooltip(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"))); @@ -2440,7 +2460,7 @@ HBoxContainer *ThemeTypeEditor::_create_property_control(Theme::DataType p_data_ item_override_button->set_tooltip(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; @@ -2464,18 +2484,19 @@ void ThemeTypeEditor::_update_type_items() { 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(Theme::get_default()->get_color(E.key, edited_type)); item_editor->set_disabled(true); } @@ -2492,9 +2513,9 @@ void ThemeTypeEditor::_update_type_items() { 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); @@ -2504,11 +2525,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(Theme::get_default()->get_constant(E.key, edited_type)); item_editor->set_editable(false); } @@ -2525,27 +2546,27 @@ void ThemeTypeEditor::_update_type_items() { 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 (Theme::get_default()->has_font(E.key, edited_type)) { + item_editor->set_edited_resource(Theme::get_default()->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); } @@ -2563,9 +2584,9 @@ void ThemeTypeEditor::_update_type_items() { 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); @@ -2575,11 +2596,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(Theme::get_default()->get_font_size(E.key, edited_type)); item_editor->set_editable(false); } @@ -2596,27 +2617,27 @@ void ThemeTypeEditor::_update_type_items() { 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 (Theme::get_default()->has_icon(E.key, edited_type)) { + item_editor->set_edited_resource(Theme::get_default()->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); } @@ -2655,35 +2676,35 @@ 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); @@ -2691,12 +2712,12 @@ void ThemeTypeEditor::_update_type_items() { 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.")); 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 (Theme::get_default()->has_stylebox(E.key, edited_type)) { + item_editor->set_edited_resource(Theme::get_default()->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); } @@ -2730,7 +2751,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); } @@ -2804,7 +2825,7 @@ void ThemeTypeEditor::_add_default_type_items() { updating = false; - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Override All Default Theme Items")); ur->add_do_method(*edited_theme, "merge_with", new_snapshot); @@ -2824,7 +2845,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Add Theme Item")); switch (p_data_type) { @@ -2869,7 +2890,7 @@ 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Override Theme Item")); switch (p_data_type) { @@ -2908,7 +2929,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Remove Theme Item")); switch (p_data_type) { @@ -2928,10 +2949,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)) { @@ -2986,7 +3003,7 @@ void ThemeTypeEditor::_item_rename_confirmed(int p_data_type, String p_item_name return; } - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Rename Theme Item")); switch (p_data_type) { @@ -3042,7 +3059,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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)); @@ -3050,7 +3067,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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)); @@ -3058,19 +3075,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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>()); @@ -3087,7 +3104,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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>()); @@ -3104,7 +3121,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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>()); @@ -3131,7 +3148,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)); @@ -3147,7 +3164,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Pin Stylebox")); ur->add_do_method(this, "_pin_leading_stylebox", p_item_name, stylebox); @@ -3169,7 +3186,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()) { @@ -3180,7 +3197,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(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); 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); @@ -3211,11 +3228,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); } @@ -3247,7 +3266,7 @@ void ThemeTypeEditor::_update_stylebox_from_leading() { } void ThemeTypeEditor::_type_variation_changed(const String p_value) { - UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); ur->create_action(TTR("Set Theme Type Variation")); if (p_value.is_empty()) { @@ -3268,7 +3287,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); } @@ -3295,9 +3314,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; } @@ -3366,6 +3382,7 @@ 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)); @@ -3397,6 +3414,7 @@ ThemeTypeEditor::ThemeTypeEditor() { 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); @@ -3440,7 +3458,7 @@ ThemeTypeEditor::ThemeTypeEditor() { 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(); @@ -3455,6 +3473,8 @@ ThemeTypeEditor::ThemeTypeEditor() { add_child(update_debounce_timer); } +/////////////////////// + void ThemeEditor::edit(const Ref<Theme> &p_theme) { if (theme == p_theme) { return; @@ -3505,8 +3525,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) { @@ -3579,7 +3599,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; @@ -3600,13 +3620,13 @@ 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)); @@ -3664,7 +3684,7 @@ ThemeEditor::ThemeEditor() { List<String> ext; ResourceLoader::get_recognized_extensions_for_type("PackedScene", &ext); for (const String &E : ext) { - preview_scene_dialog->add_filter(vformat("*.%s; %s", E, TTR("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)); @@ -3673,6 +3693,8 @@ 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)); @@ -3695,7 +3717,7 @@ bool ThemeEditorPlugin::handles(Object *p_node) const { // 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. + // This only goes one layer deep, but if required this can be extended to support, say, Font inside of Font. bool belongs_to_theme = false; if (Object::cast_to<Font>(p_node)) { diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index 4b49fbb186..9f89a047cb 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -73,7 +73,7 @@ class ThemeItemImportTree : public VBoxContainer { SELECT_IMPORT_FULL, }; - Map<ThemeItem, ItemCheckedState> selected_items; + RBMap<ThemeItem, ItemCheckedState> selected_items; LineEdit *import_items_filter = nullptr; @@ -198,6 +198,7 @@ class ThemeItemEditorDialog : public AcceptDialog { 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 = nullptr; @@ -249,10 +250,10 @@ class ThemeItemEditorDialog : public AcceptDialog { void _dialog_about_to_show(); void _update_edit_types(); void _edited_type_selected(); - void _edited_type_button_pressed(Object *p_item, int p_column, int p_id); + 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); @@ -363,7 +364,7 @@ class ThemeTypeEditor : public MarginContainer { 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(); @@ -384,7 +385,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); diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp index 252a19a7db..b5c6c6d651 100644 --- a/editor/plugins/theme_editor_preview.cpp +++ b/editor/plugins/theme_editor_preview.cpp @@ -36,6 +36,7 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "scene/gui/button.h" +#include "scene/gui/check_button.h" #include "scene/gui/color_picker.h" #include "scene/gui/progress_bar.h" #include "scene/resources/packed_scene.h" @@ -126,7 +127,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); @@ -246,7 +247,7 @@ ThemeEditorPreview::ThemeEditorPreview() { 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); @@ -269,6 +270,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); @@ -343,7 +353,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); diff --git a/editor/plugins/theme_editor_preview.h b/editor/plugins/theme_editor_preview.h index d05916afae..4d209ac788 100644 --- a/editor/plugins/theme_editor_preview.h +++ b/editor/plugins/theme_editor_preview.h @@ -38,6 +38,8 @@ #include "scene/gui/scroll_container.h" #include "scene/resources/theme.h" +class ColorPickerButton; + class ThemeEditorPreview : public VBoxContainer { GDCLASS(ThemeEditorPreview, VBoxContainer); @@ -87,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(); }; diff --git a/editor/plugins/tiles/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp index 086588f5a5..de4f3f7989 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.cpp +++ b/editor/plugins/tiles/atlas_merging_dialog.cpp @@ -33,6 +33,7 @@ #include "editor/editor_file_dialog.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_undo_redo_manager.h" #include "scene/gui/control.h" #include "scene/gui/split_container.h" @@ -62,7 +63,7 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla 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; @@ -116,12 +117,8 @@ 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_name(p_atlas_sources[0]->get_name()); - merged->set_texture(output_image_texture); + merged->set_texture(ImageTexture::create_from_image(output_image)); merged->set_texture_region_size(new_texture_region_size); } } @@ -260,7 +257,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); diff --git a/editor/plugins/tiles/atlas_merging_dialog.h b/editor/plugins/tiles/atlas_merging_dialog.h index 9502d93f6b..c7e4635d16 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.h +++ b/editor/plugins/tiles/atlas_merging_dialog.h @@ -38,6 +38,7 @@ #include "scene/resources/tile_set.h" class EditorFileDialog; +class EditorUndoRedoManager; class AtlasMergingDialog : public ConfirmationDialog { GDCLASS(AtlasMergingDialog, ConfirmationDialog); @@ -46,10 +47,10 @@ 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 = nullptr; + Ref<EditorUndoRedoManager> undo_redo; // Settings. int next_line_after_column = 30; diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index 4de2f42fe0..f119ada810 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -480,7 +480,7 @@ void TileAtlasView::_update_alternative_tiles_rect_cache() { // 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; @@ -494,7 +494,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); @@ -544,7 +544,7 @@ 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); @@ -613,32 +613,32 @@ 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_FULL_RECT); 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_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); 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); diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h index 6e74858b08..196a642283 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -69,7 +69,7 @@ private: void _pan_callback(Vector2 p_scroll_vec); void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt); - Map<Vector2, Map<int, Rect2i>> alternative_tiles_rect_cache; + HashMap<Vector2, HashMap<int, Rect2i>> alternative_tiles_rect_cache; void _update_alternative_tiles_rect_cache(); MarginContainer *margin_container = nullptr; @@ -158,4 +158,4 @@ public: 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 70bcd7e39a..b44b6fcc53 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -38,6 +38,7 @@ #include "editor/editor_node.h" #include "editor/editor_properties.h" #include "editor/editor_scale.h" +#include "editor/editor_undo_redo_manager.h" void TileDataEditor::_tile_set_changed_plan_update() { _tile_set_changed_update_needed = true; @@ -218,7 +219,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)); } @@ -248,7 +249,14 @@ void GenericTilePolygonEditor::_zoom_changed() { } void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { - UndoRedo *undo_redo = use_undo_redo ? editor_undo_redo : memnew(UndoRedo); + Ref<EditorUndoRedoManager> undo_redo; + if (use_undo_redo) { + undo_redo = editor_undo_redo; + } else { + // This nice hack allows for discarding undo actions without making code too complex. + undo_redo.instantiate(); + } + switch (p_item_pressed) { case RESET_TO_DEFAULT_TILE: { undo_redo->create_action(TTR("Reset Polygons")); @@ -322,9 +330,6 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { default: break; } - if (!use_undo_redo) { - memdelete(undo_redo); - } } void GenericTilePolygonEditor::_grab_polygon_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_point_index) { @@ -409,7 +414,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); + Ref<EditorUndoRedoManager> undo_redo; + if (use_undo_redo) { + undo_redo = editor_undo_redo; + } else { + // This nice hack allows for discarding undo actions without making code too complex. + undo_redo.instantiate(); + } + real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); hovered_polygon_index = -1; @@ -600,10 +612,6 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) } base_control->update(); - - if (!use_undo_redo) { - memdelete(undo_redo); - } } void GenericTilePolygonEditor::set_use_undo_redo(bool p_use_undo_redo) { @@ -776,13 +784,13 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { 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); @@ -803,13 +811,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); @@ -862,7 +870,7 @@ Variant TileDataDefaultEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_s 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) { 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); @@ -882,7 +890,7 @@ void TileDataDefaultEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas_ 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 = 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); @@ -897,8 +905,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()); @@ -1116,8 +1124,9 @@ 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.get_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.get_origin() + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color); } } @@ -1147,7 +1156,7 @@ 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); } @@ -1173,6 +1182,7 @@ TileDataDefaultEditor::TileDataDefaultEditor() { label = memnew(Label); label->set_text(TTR("Painting:")); + label->set_theme_type_variation("HeaderSmall"); add_child(label); toolbar->add_child(memnew(VSeparator)); @@ -1299,7 +1309,7 @@ Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_ 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) { 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); @@ -1479,7 +1489,7 @@ 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) { +void TileDataCollisionEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) { Array new_array = p_new_value; for (KeyValue<TileMapCell, Variant> &E : p_previous_values) { Array old_array = E.value; @@ -1676,10 +1686,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); @@ -1721,8 +1736,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); } } } @@ -1740,7 +1756,7 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas 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 = 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); @@ -1755,8 +1771,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()); @@ -1770,7 +1786,7 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas 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 = 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); @@ -1800,16 +1816,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); + 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; } @@ -1850,10 +1875,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); @@ -1897,8 +1928,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); } } } @@ -1926,10 +1958,11 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t 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; @@ -1958,10 +1991,11 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t 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; @@ -1970,12 +2004,17 @@ 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); + + 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); } } } @@ -2000,12 +2039,17 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); 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)); } } } @@ -2044,10 +2088,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; @@ -2085,10 +2130,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; @@ -2097,12 +2143,16 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t 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_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); } } } @@ -2118,7 +2168,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position())); 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); @@ -2133,15 +2183,16 @@ 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(); + 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_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); + 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()); + 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", 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_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)); + 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)); } } } @@ -2154,10 +2205,11 @@ 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"]); + 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(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]); } } @@ -2172,13 +2224,15 @@ 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]); } } @@ -2195,7 +2249,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position())); 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); @@ -2220,24 +2274,34 @@ 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(); + 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() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + + 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)); } } } @@ -2267,10 +2331,11 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi 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; @@ -2300,10 +2365,11 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi 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; @@ -2312,12 +2378,18 @@ 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); + + 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); } } } @@ -2343,12 +2415,18 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); 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)); } } } @@ -2380,7 +2458,7 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi 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; @@ -2405,10 +2483,11 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi // 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; @@ -2416,12 +2495,17 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi // 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); + + 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); } } } @@ -2437,6 +2521,7 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi 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); + 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]); @@ -2452,13 +2537,15 @@ 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]); } } @@ -2491,7 +2578,8 @@ TileDataTerrainsEditor::TileDataTerrainsEditor() { undo_redo = EditorNode::get_undo_redo(); label = memnew(Label); - label->set_text("Painting:"); + label->set_text(TTR("Painting:")); + label->set_theme_type_variation("HeaderSmall"); add_child(label); // Toolbar @@ -2571,7 +2659,7 @@ Variant TileDataNavigationEditor::_get_value(TileSetAtlasSource *p_tile_set_atla 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) { 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); diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h index 3ac9eacb05..c1560138b2 100644 --- a/editor/plugins/tiles/tile_data_editors.h +++ b/editor/plugins/tiles/tile_data_editors.h @@ -39,6 +39,8 @@ #include "scene/gui/control.h" #include "scene/gui/label.h" +class EditorUndoRedoManager; + class TileDataEditor : public VBoxContainer { GDCLASS(TileDataEditor, VBoxContainer); @@ -71,7 +73,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); @@ -93,7 +95,7 @@ private: bool multiple_polygon_mode = false; bool use_undo_redo = true; - UndoRedo *editor_undo_redo = nullptr; + Ref<EditorUndoRedoManager> editor_undo_redo; // UI int hovered_polygon_index = -1; @@ -108,8 +110,8 @@ 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; @@ -132,9 +134,9 @@ private: 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); @@ -206,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); @@ -214,7 +216,7 @@ private: protected: DummyObject *dummy_object = memnew(DummyObject); - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; StringName type; String property; @@ -224,7 +226,7 @@ protected: 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; }; @@ -276,10 +278,10 @@ 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 = nullptr; + Ref<EditorUndoRedoManager> undo_redo; virtual void _tile_set_changed() override; @@ -301,7 +303,7 @@ class TileDataCollisionEditor : public TileDataDefaultEditor { // UI 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); @@ -311,10 +313,10 @@ class TileDataCollisionEditor : public TileDataDefaultEditor { 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 = nullptr; + Ref<EditorUndoRedoManager> undo_redo; virtual void _tile_set_changed() override; @@ -348,7 +350,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; // UI @@ -366,7 +368,7 @@ protected: void _notification(int p_what); - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; public: virtual Control *get_toolbar() override { return toolbar; }; @@ -396,10 +398,10 @@ 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 = nullptr; + Ref<EditorUndoRedoManager> undo_redo; virtual void _tile_set_changed() override; diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index c1a95c11f6..3f355a1ed2 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -35,6 +35,7 @@ #include "editor/editor_node.h" #include "editor/editor_resource_preview.h" #include "editor/editor_scale.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "scene/2d/camera_2d.h" @@ -76,7 +77,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) { @@ -84,7 +85,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) { @@ -92,7 +93,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) { @@ -101,7 +102,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(); } @@ -443,7 +444,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(); @@ -461,6 +466,7 @@ void TileMapEditorTilesPlugin::_update_theme() { 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")); } @@ -496,8 +502,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); } @@ -506,9 +512,9 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p // Delete selected tiles. if (!tile_map_selection.is_empty()) { 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(); @@ -537,9 +543,9 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p // Delete selected tiles. if (!tile_map_selection.is_empty()) { 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(); @@ -557,7 +563,7 @@ 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; @@ -574,7 +580,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_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; @@ -623,8 +629,8 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p _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); } @@ -643,7 +649,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p 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; @@ -670,7 +676,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_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; @@ -741,7 +747,7 @@ 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) { @@ -763,7 +769,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over // 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(); 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); @@ -780,8 +786,8 @@ 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); @@ -827,7 +833,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over // 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); } @@ -870,7 +876,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over Transform2D tile_xform; tile_xform.set_origin(tile_map->map_to_world(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)) { @@ -879,6 +885,9 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over if (atlas_source) { // Get tile data. 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()); @@ -981,15 +990,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. @@ -998,10 +1007,10 @@ 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)); for (int i = 0; i < line.size(); i++) { @@ -1030,15 +1039,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. @@ -1051,7 +1060,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. @@ -1059,9 +1068,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++) { @@ -1089,21 +1098,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. @@ -1123,7 +1132,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()) { @@ -1134,7 +1143,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 { @@ -1180,7 +1189,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 { @@ -1273,8 +1282,8 @@ void TileMapEditorTilesPlugin::_stop_dragging() { 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. @@ -1285,7 +1294,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { // 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); @@ -1294,7 +1303,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(); @@ -1306,11 +1315,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. @@ -1373,7 +1382,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) { @@ -1385,7 +1394,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->world_to_map(drag_start_mouse_pos), tile_map->world_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) { @@ -1471,7 +1480,7 @@ 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; E = 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()) || @@ -1495,7 +1504,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) { @@ -1507,6 +1516,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)); @@ -1524,8 +1538,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); } @@ -1548,9 +1562,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; @@ -1558,7 +1572,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); @@ -1573,12 +1587,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 { @@ -1670,14 +1684,14 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { // Draw the selection. 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); - 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); + tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E.get_atlas_coords(), frame), color, false); } } } @@ -1701,7 +1715,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { 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)); @@ -1711,8 +1725,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) { + tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E), selection_rect_color, false); } } } @@ -1858,9 +1872,9 @@ 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); } @@ -1962,8 +1976,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; } @@ -2018,7 +2032,6 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { // --- Toolbar --- toolbar = memnew(HBoxContainer); - toolbar->set_h_size_flags(Control::SIZE_EXPAND_FILL); HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer); @@ -2103,11 +2116,12 @@ 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(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_label = memnew(Label); @@ -2185,7 +2199,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { 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, source_sort_button)); + 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); @@ -2219,7 +2233,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { 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); @@ -2249,7 +2263,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { 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("empty_clicked", callable_mp(this, &TileMapEditorTilesPlugin::patterns_item_list_empty_clicked)); patterns_bottom_panel->add_child(patterns_item_list); patterns_help_label = memnew(Label); @@ -2310,161 +2324,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, false); - - // 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> &E : terrain_fill_output) { + if (painted_set.has(E.key)) { + // Paint a random tile with the correct terrain for the painted path. + output[E.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E.value); + } else { + // Avoids updating the painted path from the output if the new pattern is the same as before. + bool keep_old = false; + TileMapCell cell = tile_map->get_cell(tile_map_layer, E.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_terrains_pattern() == E.value) { + keep_old = true; } - break; } } + if (!keep_old) { + output[E.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E.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> &E : terrain_fill_output) { + if (painted_set.has(E.key)) { + // Paint a random tile with the correct terrain for the painted path. + output[E.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E.value); + } else { + // Avoids updating the painted path from the output if the new pattern is the same as before. + TileMapCell cell = tile_map->get_cell(tile_map_layer, E.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_terrains_pattern() == E.value)) { + output[E.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E.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); - } else { - terrains_pattern = selected_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 + TileSet::TerrainsPattern terrains_pattern; + if (p_erase) { + terrains_pattern = 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; + Vector<Vector2i> line = TileMapEditor::get_line(tile_map, p_start_cell, p_end_cell); + return _draw_terrain_pattern(line, selected_terrain_set, 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; @@ -2472,24 +2463,35 @@ 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)); } } - return _draw_terrains(to_draw, selected_terrain_set); + + 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 + TileSet::TerrainsPattern terrains_pattern; + if (p_erase) { + terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); + } else { + terrains_pattern = selected_terrains_pattern; + } + return _draw_terrain_pattern(to_draw, selected_terrain_set, terrains_pattern); + } } -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); @@ -2503,7 +2505,7 @@ Set<Vector2i> TileMapEditorTerrainsPlugin::_get_cells_for_bucket_fill(Vector2i p 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(); } @@ -2514,10 +2516,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()) { @@ -2592,31 +2594,34 @@ 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 (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 + TileSet::TerrainsPattern terrains_pattern; + if (p_erase) { + terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); + } else { + terrains_pattern = selected_terrains_pattern; + } + return _draw_terrain_pattern(cells_to_draw_as_vector, selected_terrain_set, terrains_pattern); } - - return _draw_terrains(to_draw, selected_terrain_set); } void TileMapEditorTerrainsPlugin::_stop_dragging() { @@ -2685,11 +2690,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 { @@ -2707,7 +2714,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->world_to_map(drag_start_mouse_pos), tile_map->world_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) { @@ -2719,7 +2726,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->world_to_map(drag_start_mouse_pos), tile_map->world_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) { @@ -2762,22 +2769,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(); + } } } } @@ -2818,7 +2836,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->world_to_map(drag_last_mouse_pos), tile_map->world_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); @@ -2854,7 +2872,7 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> } 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 (selected_terrain_set < 0 || selected_terrain < 0 || (selected_type == SELECTED_TYPE_PATTERN && !selected_terrains_pattern.is_valid())) { return true; } @@ -2863,27 +2881,27 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> drag_modified.clear(); Vector2i cell = tile_map->world_to_map(mpos); - Map<Vector2i, TileMapCell> to_draw = _draw_line(cell, cell, drag_erasing); + 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()) { + 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()) { + 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; @@ -2892,7 +2910,7 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_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; @@ -2948,7 +2966,7 @@ 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) { @@ -2985,7 +3003,7 @@ void TileMapEditorTerrainsPlugin::forward_canvas_draw_over_viewport(Control *p_o rect.set_end(tile_map->world_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)); @@ -3094,11 +3112,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); } @@ -3180,25 +3205,38 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() { 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)); + // 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[selected_terrain_set][selected_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(selected_terrain_set, bit) && E.get_terrain_peering_bit(bit) == selected_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; @@ -3230,12 +3268,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); } } @@ -3253,6 +3292,8 @@ void TileMapEditorTerrainsPlugin::_update_theme() { 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) { @@ -3292,7 +3333,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); @@ -3385,7 +3426,7 @@ void TileMapEditor::_notification(int p_what) { 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"))); + toggle_highlight_selected_layer_button->set_icon(get_theme_icon(SNAME("TileMapHighlightSelected"), SNAME("EditorIcons"))); } break; case NOTIFICATION_INTERNAL_PROCESS: { @@ -3417,66 +3458,16 @@ void TileMapEditor::_notification(int p_what) { 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(); } @@ -3659,8 +3650,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; @@ -3687,39 +3676,30 @@ 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); - // 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")); - } - - // 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_disabled(true); + layers_selection_button->set_text(TTR("No Layers")); } - 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); + Ref<EditorUndoRedoManager> undo_redo = Object::cast_to<EditorUndoRedoManager>(p_undo_redo); + ERR_FAIL_COND(undo_redo.is_null()); TileMap *tile_map = Object::cast_to<TileMap>(p_edited); if (!tile_map) { @@ -3989,7 +3969,6 @@ TileMapEditor::TileMapEditor() { // TabBar. tabs_bar = memnew(TabBar); - tabs_bar->set_tab_alignment(TabBar::ALIGNMENT_CENTER); tabs_bar->set_clip_tabs(false); for (int plugin_index = 0; plugin_index < tile_map_editor_plugins.size(); plugin_index++) { Vector<TileMapEditorPlugin::TabData> tabs_vector = tile_map_editor_plugins[plugin_index]->get_tabs(); @@ -4018,31 +3997,23 @@ TileMapEditor::TileMapEditor() { } // 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); + tile_map_toolbar->add_spacer(); // 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_flag(Window::FLAG_POPUP, 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(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(TTR("Highlight Selected TileMap Layer")); + tile_map_toolbar->add_child(toggle_highlight_selected_layer_button); tile_map_toolbar->add_child(memnew(VSeparator)); diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index 3a0293f48f..9a47d8bbc4 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -40,13 +40,14 @@ #include "scene/gui/check_box.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 UndoRedo; +class EditorUndoRedoManager; class TileMapEditorPlugin : public Object { public: @@ -69,7 +70,7 @@ class TileMapEditorTilesPlugin : public TileMapEditorPlugin { GDCLASS(TileMapEditorTilesPlugin, TileMapEditorPlugin); private: - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> 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; @@ -92,7 +93,7 @@ private: VSeparator *tools_settings_vsep_2 = nullptr; CheckBox *bucket_contiguous_checkbox = nullptr; - CheckBox *random_tile_checkbox = nullptr; + Button *random_tile_toggle = nullptr; float scattering = 0.0; Label *scatter_label = nullptr; SpinBox *scatter_spinbox = nullptr; @@ -120,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(); @@ -144,6 +145,8 @@ 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(); @@ -187,7 +190,7 @@ private: 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 = nullptr; @@ -220,7 +223,7 @@ class TileMapEditorTerrainsPlugin : public TileMapEditorPlugin { GDCLASS(TileMapEditorTerrainsPlugin, TileMapEditorPlugin); private: - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> 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; @@ -263,17 +266,25 @@ 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(); @@ -282,7 +293,7 @@ private: 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(); @@ -306,7 +317,7 @@ class TileMapEditor : public VBoxContainer { GDCLASS(TileMapEditor, VBoxContainer); private: - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; bool tileset_changed_needs_update = false; ObjectID tile_map_id; int tile_map_layer = -1; @@ -317,12 +328,9 @@ private: // Toolbar. HBoxContainer *tile_map_toolbar = nullptr; - PopupMenu *layers_selection_popup = nullptr; - Button *layers_selection_button = nullptr; - Button *toogle_highlight_selected_layer_button = nullptr; - 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 = nullptr; void _on_grid_toggled(bool p_pressed); @@ -369,4 +377,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 62f3bd6356..9e4c29fa79 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp @@ -32,8 +32,13 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_undo_redo_manager.h" + +void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list, MouseButton p_mouse_button_index) { + 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()); @@ -336,7 +341,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); @@ -347,7 +352,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); @@ -358,7 +363,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); diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.h b/editor/plugins/tiles/tile_proxies_manager_dialog.h index 00866544be..511e442a10 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.h +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.h @@ -43,7 +43,7 @@ private: int commited_actions_count = 0; Ref<TileSet> tile_set; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; TileMapCell from; TileMapCell to; @@ -61,7 +61,7 @@ private: EditorPropertyInteger *alternative_to_property_editor = nullptr; PopupMenu *popup_menu = nullptr; - void _right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list); + void _right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list, MouseButton p_mouse_button_index); 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 44b18f48fc..09722d3d65 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -97,9 +97,9 @@ 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::VECTOR2I, "margins", PROPERTY_HINT_NONE, "suffix:px")); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "suffix:px")); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "suffix:px")); p_list->push_back(PropertyInfo(Variant::BOOL, "use_texture_padding", PROPERTY_HINT_NONE, "")); } @@ -270,9 +270,9 @@ 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 = tile_set_atlas_source->get_tile_data(coords, alternative); @@ -354,11 +354,11 @@ 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 = tile_set_atlas_source->get_tile_data(coords, alternative); ERR_FAIL_COND_V(!tile_data, false); @@ -401,15 +401,15 @@ 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::VECTOR2I, "animation_separation", PROPERTY_HINT_NONE, "suffix:px")); 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_")); // 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/duration", i), PROPERTY_HINT_NONE, "suffix:s")); } } } @@ -426,12 +426,12 @@ 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 = tile_set_atlas_source->get_tile_data(coords, alternative); ERR_FAIL_COND(!tile_data); @@ -439,7 +439,7 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro 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()) { 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")) { @@ -473,18 +473,18 @@ 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 = tile_set_atlas_source->get_tile_data(coords, alternative); @@ -495,12 +495,12 @@ 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 = tile_set_atlas_source->get_tile_data(coords, alternative); @@ -541,7 +541,7 @@ 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; E = 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); @@ -735,15 +735,15 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { // --- Custom Data --- ADD_TILE_DATA_EDITOR_GROUP("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()) { + if (tile_set->get_custom_data_layer_name(i).is_empty()) { ADD_TILE_DATA_EDITOR(group, vformat("Custom Data %d", i), vformat("custom_data_%d", i)); } else { - ADD_TILE_DATA_EDITOR(group, tile_set->get_custom_data_name(i), vformat("custom_data_%d", i)); + ADD_TILE_DATA_EDITOR(group, tile_set->get_custom_data_layer_name(i), vformat("custom_data_%d", i)); } if (!tile_data_editors.has(vformat("custom_data_%d", i))) { 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->setup_property_editor(tile_set->get_custom_data_layer_type(i), vformat("custom_data_%d", i), tile_set->get_custom_data_layer_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; @@ -913,7 +913,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); @@ -1313,9 +1313,9 @@ void TileSetAtlasSourceEditor::_end_dragging() { 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; @@ -1328,10 +1328,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)) { @@ -1370,9 +1370,9 @@ void TileSetAtlasSourceEditor::_end_dragging() { 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)); @@ -1384,8 +1384,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)) { @@ -1523,9 +1523,9 @@ void TileSetAtlasSourceEditor::_end_dragging() { tile_atlas_control->set_default_cursor_shape(CURSOR_ARROW); } -Map<Vector2i, List<const PropertyInfo *>> TileSetAtlasSourceEditor::_group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas) { +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) { @@ -1544,13 +1544,13 @@ void TileSetAtlasSourceEditor::_menu_option(int 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); @@ -1569,8 +1569,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); @@ -1608,13 +1608,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); @@ -1658,9 +1658,9 @@ 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; } @@ -1672,8 +1672,8 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { // 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++) { @@ -1722,9 +1722,9 @@ 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++) { + tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(E, frame), Color(0.0, 0.0, 0.0), false); } } } else if (drag_type == DRAG_TYPE_RECT_SELECT || drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT) { @@ -1739,7 +1739,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { color = selection_color.lightened(0.2); } - 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)); @@ -1749,8 +1749,8 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { } } - for (Set<Vector2i>::Element *E = to_paint.front(); E; E = E->next()) { - Vector2i coords = E->get(); + for (const Vector2i &E : to_paint) { + Vector2i coords = E; tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(coords), color, false); } } else if (drag_type == DRAG_TYPE_CREATE_TILES_USING_RECT) { @@ -1822,7 +1822,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw() { Vector2i position = texture_region.get_center() + tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); 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; @@ -1837,19 +1837,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_effective_texture_offset(E.tile, 0); 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); } @@ -1886,6 +1886,12 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<In tile_atlas_control_unscaled->update(); alternative_tiles_control->update(); alternative_tiles_control_unscaled->update(); + + if (drag_type == DRAG_TYPE_MAY_POPUP_MENU) { + if (Vector2(drag_start_mouse_pos).distance_to(tile_atlas_control->get_local_mouse_position()) > 5.0 * EDSCALE) { + drag_type = DRAG_TYPE_NONE; + } + } } Ref<InputEventMouseButton> mb = p_event; @@ -1911,7 +1917,10 @@ 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 + 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(); @@ -1962,8 +1971,8 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() { } // 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()) { @@ -1989,7 +1998,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { Vector2 position = rect.get_center(); 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; @@ -2005,20 +2014,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); + Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E.tile, E.alternative); Vector2 position = rect.get_center(); 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); } } @@ -2049,14 +2058,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); + Ref<EditorUndoRedoManager> undo_redo = Object::cast_to<EditorUndoRedoManager>(p_undo_redo); + ERR_FAIL_COND(!undo_redo.is_valid()); #define ADD_UNDO(obj, property) undo_redo->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->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(); @@ -2071,13 +2082,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); @@ -2085,6 +2098,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->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()); @@ -2111,8 +2127,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 } @@ -2222,7 +2238,7 @@ void TileSetAtlasSourceEditor::_auto_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); for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { Vector2i coords = tile_set_atlas_source->get_tile_id(i); @@ -2334,6 +2350,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { // Tile inspector. tile_inspector_label = memnew(Label); tile_inspector_label->set_text(TTR("Tile Properties:")); + tile_inspector_label->set_theme_type_variation("HeaderSmall"); middle_vbox_container->add_child(tile_inspector_label); tile_proxy_object = memnew(AtlasTileProxyObject(this)); @@ -2349,7 +2366,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_inspector_no_tile_selected_label = memnew(Label); 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. @@ -2357,6 +2374,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_data_editors_label = memnew(Label); tile_data_editors_label->set_text(TTR("Paint Properties:")); + tile_data_editors_label->set_theme_type_variation("HeaderSmall"); middle_vbox_container->add_child(tile_data_editors_label); tile_data_editor_dropdown_button = memnew(Button); @@ -2367,7 +2385,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { 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)); @@ -2380,6 +2398,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { // Atlas source inspector. atlas_source_inspector_label = memnew(Label); atlas_source_inspector_label->set_text(TTR("Atlas Properties:")); + atlas_source_inspector_label->set_theme_type_variation("HeaderSmall"); middle_vbox_container->add_child(atlas_source_inspector_label); atlas_source_proxy_object = memnew(TileSetAtlasSourceProxyObject()); @@ -2401,7 +2420,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { 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->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); @@ -2505,7 +2524,7 @@ 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->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); 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); @@ -2522,7 +2541,7 @@ 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->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); 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); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index b26b7edbfa..badb702e29 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -88,7 +88,7 @@ public: 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); @@ -99,10 +99,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; @@ -114,7 +114,7 @@ private: TileSetAtlasSource *tile_set_atlas_source = nullptr; int tile_set_atlas_source_id = TileSet::INVALID_SOURCE; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; bool tile_set_changed_needs_update = false; @@ -130,7 +130,7 @@ private: // -- 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(); @@ -182,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 { @@ -215,7 +215,7 @@ private: MenuButton *tool_advanced_menu_buttom = nullptr; // Selection. - Set<TileSelection> selection; + RBSet<TileSelection> selection; void _set_selection_from_array(Array p_selection); Array _get_selection_as_array(); diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index fb4a563992..8d04dd3121 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -36,6 +36,7 @@ #include "editor/editor_file_system.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/control.h" @@ -405,8 +406,8 @@ 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); + Ref<EditorUndoRedoManager> undo_redo = Object::cast_to<EditorUndoRedoManager>(p_undo_redo); + ERR_FAIL_COND(undo_redo.is_null()); TileSet *tile_set = Object::cast_to<TileSet>(p_edited); if (!tile_set) { @@ -505,7 +506,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])); } } @@ -513,7 +514,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])); } } @@ -586,8 +587,8 @@ void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_ } 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); + Ref<EditorUndoRedoManager> undo_redo = Object::cast_to<EditorUndoRedoManager>(p_undo_redo); + ERR_FAIL_COND(undo_redo.is_null()); #define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); TileSet *tile_set = Object::cast_to<TileSet>(p_edited); @@ -607,9 +608,10 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p 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])); } } @@ -709,9 +711,9 @@ TileSetEditor::TileSetEditor() { 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, source_sort_button)); + 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), varray(-1)); + 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); split_container_left_side->add_child(sources_list); diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index e633de37b0..3b9b80dac4 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -39,6 +39,8 @@ #include "tile_set_atlas_source_editor.h" #include "tile_set_scenes_collection_source_editor.h" +class EditorUndoRedoManager; + class TileSetEditor : public VBoxContainer { GDCLASS(TileSetEditor, VBoxContainer); @@ -58,7 +60,7 @@ private: TileSetAtlasSourceEditor *tile_set_atlas_source_editor = nullptr; TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor = nullptr; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; 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; @@ -108,4 +110,4 @@ public: ~TileSetEditor(); }; -#endif // TILE_SET_EDITOR_PLUGIN_H +#endif // TILE_SET_EDITOR_H 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 657bfca032..0284b45c0f 100644 --- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h @@ -97,7 +97,7 @@ private: TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr; int tile_set_source_id = -1; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; bool tile_set_scenes_collection_source_changed_needs_update = false; @@ -142,4 +142,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 543304346e..b5134f6893 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -116,21 +116,18 @@ void TilesEditorPlugin::_thread() { // Add the viewport at the last moment to avoid rendering too early. EditorNode::get_singleton()->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_ONESHOT); 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 { @@ -158,6 +155,9 @@ 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) { @@ -184,12 +184,14 @@ void TilesEditorPlugin::_notification(int p_what) { } void TilesEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { + is_visible = p_visible; + + if (is_visible) { // 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) { + if (tile_map && !is_editing_tile_set) { EditorNode::get_singleton()->make_bottom_panel_item_visible(tilemap_editor); } else { EditorNode::get_singleton()->make_bottom_panel_item_visible(tileset_editor); @@ -229,14 +231,14 @@ void TilesEditorPlugin::synchronize_sources_list(Object *p_current_list, Object } 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 { - // 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; - 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); @@ -348,6 +350,8 @@ 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(); @@ -362,6 +366,7 @@ void TilesEditorPlugin::edit(Object *p_object) { tile_map_id = ObjectID(); } } + is_editing_tile_set = true; EditorNode::get_singleton()->make_bottom_panel_item_visible(tileset_editor); } } diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h index a22e782b34..b1fe6f8df6 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.h +++ b/editor/plugins/tiles/tiles_editor_plugin.h @@ -53,9 +53,12 @@ public: }; private: + 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 = nullptr; TileMapEditor *tilemap_editor = nullptr; diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index 443d5975cd..6db499f2c7 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -35,6 +35,8 @@ #include "editor/editor_file_system.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "scene/gui/separator.h" VersionControlEditorPlugin *VersionControlEditorPlugin::singleton = nullptr; @@ -240,7 +242,7 @@ void VersionControlEditorPlugin::_view_file_diff() { } void VersionControlEditorPlugin::_display_file_diff(String p_file_path) { - Array diff_content = EditorVCSInterface::get_singleton()->get_file_diff(p_file_path); + TypedArray<Dictionary> diff_content = EditorVCSInterface::get_singleton()->get_file_diff(p_file_path); diff_file_name->set_text(p_file_path); diff --git a/editor/plugins/version_control_editor_plugin.h b/editor/plugins/version_control_editor_plugin.h index 39a56de772..fa721268ba 100644 --- a/editor/plugins/version_control_editor_plugin.h +++ b/editor/plugins/version_control_editor_plugin.h @@ -148,4 +148,4 @@ public: 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 dc07ac7c39..e048ee2698 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -40,9 +40,18 @@ #include "editor/editor_node.h" #include "editor/editor_properties.h" #include "editor/editor_scale.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/plugins/curve_editor_plugin.h" +#include "editor/plugins/shader_editor_plugin.h" #include "scene/animation/animation_player.h" +#include "scene/gui/button.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/popup.h" +#include "scene/gui/rich_text_label.h" +#include "scene/gui/tree.h" #include "scene/gui/view_panner.h" #include "scene/main/window.h" #include "scene/resources/visual_shader_nodes.h" @@ -72,6 +81,10 @@ 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)) { @@ -86,17 +99,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() { } @@ -115,6 +117,10 @@ void VisualShaderGraphPlugin::_bind_methods() { 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); } @@ -148,6 +154,7 @@ void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p if (links[p_node_id].preview_pos != -1) { links[p_node_id].graph_node->move_child(vbox, links[p_node_id].preview_pos); } + links[p_node_id].graph_node->set_slot_draw_stylebox(vbox->get_index(), false); Control *offset = memnew(Control); offset->set_custom_minimum_size(Size2(0, 5 * EDSCALE)); @@ -185,15 +192,11 @@ void VisualShaderGraphPlugin::set_input_port_default_value(VisualShader::Type p_ switch (p_value.get_type()) { case Variant::COLOR: { - VisualShaderEditor *editor = VisualShaderEditor::get_singleton(); - if (!editor) { - break; - } button->set_custom_minimum_size(Size2(30, 0) * EDSCALE); Callable ce = callable_mp(editor, &VisualShaderEditor::_draw_color_over_button); if (!button->is_connected("draw", ce)) { - button->connect("draw", ce, varray(button, p_value)); + button->connect("draw", ce.bind(button, p_value)); } } break; case Variant::BOOL: { @@ -324,7 +327,7 @@ void VisualShaderGraphPlugin::make_dirty(bool 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) { @@ -336,10 +339,6 @@ void VisualShaderGraphPlugin::register_uniform_name(int p_node_id, LineEdit *p_u } void VisualShaderGraphPlugin::update_theme() { - VisualShaderEditor *editor = VisualShaderEditor::get_singleton(); - if (!editor) { - return; - } 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 @@ -350,10 +349,6 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (!visual_shader.is_valid() || p_type != visual_shader->get_shader_type()) { return; } - VisualShaderEditor *editor = VisualShaderEditor::get_singleton(); - if (!editor) { - return; - } GraphEdit *graph = editor->graph; if (!graph) { return; @@ -366,8 +361,6 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { Control *offset; - static Ref<StyleBoxEmpty> label_style = make_empty_stylebox(2, 1, 2, 1); - static const Color type_color[] = { Color(0.38, 0.85, 0.96), // scalar (float) Color(0.49, 0.78, 0.94), // scalar (int) @@ -386,6 +379,14 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { "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()); @@ -406,8 +407,10 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { custom_node->_set_initialized(true); } + // Create graph node. GraphNode *node = memnew(GraphNode); graph->add_child(node); + node->set_theme(vstheme); editor->_update_created_node(node); register_link(p_type, p_id, vsnode.ptr(), node); @@ -415,7 +418,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { size = resizable_node->get_size(); node->set_resizable(true); - node->connect("resize_request", callable_mp(editor, &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(); @@ -427,10 +430,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(editor, &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(editor, &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; @@ -463,6 +466,12 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { node->set_custom_minimum_size(Size2(200 * EDSCALE, 0)); } + Ref<VisualShaderNodeUniformRef> uniform_ref = vsnode; + if (uniform_ref.is_valid()) { + uniform_ref->set_shader_rid(visual_shader->get_rid()); + uniform_ref->update_uniform_type(); + } + Ref<VisualShaderNodeUniform> uniform = vsnode; HBoxContainer *hb = nullptr; @@ -471,8 +480,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { register_uniform_name(p_id, uniform_name); uniform_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); uniform_name->set_text(uniform->get_uniform_name()); - uniform_name->connect("text_submitted", callable_mp(editor, &VisualShaderEditor::_uniform_line_edit_changed), varray(p_id)); - uniform_name->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_uniform_line_edit_focus_out), varray(uniform_name, p_id)); + uniform_name->connect("text_submitted", callable_mp(editor, &VisualShaderEditor::_uniform_line_edit_changed).bind(p_id)); + uniform_name->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_uniform_line_edit_focus_out).bind(uniform_name, p_id)); if (vsnode->get_output_port_count() == 1 && vsnode->get_output_port_name(0) == "") { hb = memnew(HBoxContainer); @@ -512,7 +521,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { 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, varray(p_id)); + curve->get_texture()->connect("changed", ce.bind(p_id)); } CurveEditor *curve_editor = memnew(CurveEditor); @@ -530,7 +539,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { 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, varray(p_id)); + curve_xyz->get_texture()->connect("changed", ce.bind(p_id)); } CurveEditor *curve_editor_x = memnew(CurveEditor); @@ -593,14 +602,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(editor, &VisualShaderEditor::_add_input_port), varray(p_id, group_node->get_free_input_port_id(), VisualShaderNode::PORT_TYPE_VECTOR_3D, 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(editor, &VisualShaderEditor::_add_output_port), varray(p_id, group_node->get_free_output_port_id(), VisualShaderNode::PORT_TYPE_VECTOR_3D, 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); @@ -708,7 +717,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(editor, &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 { @@ -733,32 +742,32 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { 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(editor, &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(editor, &VisualShaderEditor::_change_input_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED); - name_box->connect("focus_exited", callable_mp(editor, &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(editor, &VisualShaderEditor::_remove_input_port), varray(p_id, i), CONNECT_DEFERRED); + 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->is_input_port_default(i, mode) && !port_left_used) { Label *hint_label = memnew(Label); 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", label_style); + hint_label->add_theme_style_override("normal", editor->get_theme_stylebox(SNAME("label_style"), SNAME("VShaderEditor"))); hb->add_child(hint_label); } } @@ -773,7 +782,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { 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(editor, &VisualShaderEditor::_remove_output_port), varray(p_id, i), CONNECT_DEFERRED); + 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); @@ -781,8 +790,8 @@ 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(editor, &VisualShaderEditor::_change_output_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED); - name_box->connect("focus_exited", callable_mp(editor, &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); @@ -795,11 +804,11 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { 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(editor, &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); } } @@ -813,7 +822,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { expand->set_pressed_texture(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(editor, &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) { @@ -825,7 +834,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { register_output_port(p_id, j, preview); - preview->connect("pressed", callable_mp(editor, &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); } } @@ -942,12 +951,12 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (vsnode->get_output_port_for_preview() >= 0) { show_port_preview(p_type, p_id, vsnode->get_output_port_for_preview()); + } 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(mode, p_type); if (!error.is_empty()) { Label *error_label = memnew(Label); @@ -1007,7 +1016,7 @@ 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(editor, &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 (is_comment) { @@ -1024,10 +1033,6 @@ void VisualShaderGraphPlugin::remove_node(VisualShader::Type p_type, int 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) { - VisualShaderEditor *editor = VisualShaderEditor::get_singleton(); - if (!editor) { - return; - } GraphEdit *graph = editor->graph; if (!graph) { return; @@ -1044,10 +1049,6 @@ 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) { - VisualShaderEditor *editor = VisualShaderEditor::get_singleton(); - if (!editor) { - return; - } GraphEdit *graph = editor->graph; if (!graph) { return; @@ -1074,6 +1075,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) { @@ -1091,27 +1113,6 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) { if (!visual_shader->is_connected("changed", ce)) { visual_shader->connect("changed", ce); } -#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; - } - } - } -#endif visual_shader->set_graph_offset(graph->get_scroll_ofs() / EDSCALE); _set_mode(visual_shader->get_mode()); } else { @@ -1359,7 +1360,7 @@ void VisualShaderEditor::_update_options_menu() { static bool low_driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name") == "opengl3"; - Map<String, TreeItem *> folders; + HashMap<String, TreeItem *> folders; int current_func = -1; @@ -1612,7 +1613,7 @@ void VisualShaderEditor::_update_created_node(GraphNode *node) { } void VisualShaderEditor::_update_uniforms(bool p_update_refs) { - VisualShaderNodeUniformRef::clear_uniforms(); + VisualShaderNodeUniformRef::clear_uniforms(visual_shader->get_rid()); for (int t = 0; t < VisualShader::TYPE_MAX; t++) { Vector<int> tnodes = visual_shader->get_node_list((VisualShader::Type)t); @@ -1650,7 +1651,7 @@ void VisualShaderEditor::_update_uniforms(bool p_update_refs) { } else { uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_SAMPLER; } - VisualShaderNodeUniformRef::add_uniform(uniform->get_uniform_name(), uniform_type); + VisualShaderNodeUniformRef::add_uniform(visual_shader->get_rid(), uniform->get_uniform_name(), uniform_type); } } } @@ -1659,7 +1660,7 @@ void VisualShaderEditor::_update_uniforms(bool p_update_refs) { } } -void VisualShaderEditor::_update_uniform_refs(Set<String> &p_deleted_names) { +void VisualShaderEditor::_update_uniform_refs(HashSet<String> &p_deleted_names) { for (int i = 0; i < VisualShader::TYPE_MAX; i++) { VisualShader::Type type = VisualShader::Type(i); @@ -1734,6 +1735,8 @@ void VisualShaderEditor::_update_graph() { float graph_minimap_opacity = EditorSettings::get_singleton()->get("editors/visual_editors/minimap_opacity"); graph->set_minimap_opacity(graph_minimap_opacity); + float graph_lines_curvature = EditorSettings::get_singleton()->get("editors/visual_editors/lines_curvature"); + graph->set_connection_lines_curvature(graph_lines_curvature); } VisualShader::Type VisualShaderEditor::get_current_shader_type() const { @@ -2277,7 +2280,7 @@ void VisualShaderEditor::_uniform_line_edit_changed(const String &p_text, int p_ undo_redo->add_do_method(this, "_update_uniforms", true); undo_redo->add_undo_method(this, "_update_uniforms", true); - Set<String> changed_names; + HashSet<String> changed_names; changed_names.insert(node->get_uniform_name()); _update_uniform_refs(changed_names); @@ -2296,10 +2299,8 @@ 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()); @@ -2307,30 +2308,51 @@ void VisualShaderEditor::_port_edited() { 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); - Ref<VisualShaderNode> vsn = visual_shader->get_node(type, p_node); + 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)); + + 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; } @@ -2653,7 +2675,6 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, Stri _setup_node(vsn, p_ops); } VisualShaderNodeUniformRef *uniform_ref = Object::cast_to<VisualShaderNodeUniformRef>(vsn); - if (uniform_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; @@ -3097,7 +3118,7 @@ void VisualShaderEditor::_delete_nodes(int p_type, const List<int> &p_nodes) { } } - Set<String> uniform_names; + HashSet<String> uniform_names; for (const int &F : p_nodes) { Ref<VisualShaderNode> node = visual_shader->get_node(type, F); @@ -3201,11 +3222,11 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { undo_redo->create_action(TTR("Convert Uniform 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_uniforms : 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; @@ -3398,16 +3419,23 @@ void VisualShaderEditor::_delete_node_request(int p_type, int p_node) { 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()) { @@ -3653,6 +3681,9 @@ void VisualShaderEditor::_notification(int 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(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); graph->set_warped_panning(bool(EditorSettings::get_singleton()->get("editors/panning/warped_mouse_panning"))); + graph->set_minimap_opacity(EditorSettings::get_singleton()->get("editors/visual_editors/minimap_opacity")); + graph->set_connection_lines_curvature(EditorSettings::get_singleton()->get("editors/visual_editors/lines_curvature")); + _update_graph(); } break; case NOTIFICATION_ENTER_TREE: { @@ -3771,7 +3802,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)); @@ -3850,9 +3881,9 @@ 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) { if (item.disabled) { @@ -4033,7 +4064,7 @@ void VisualShaderEditor::_input_select_item(Ref<VisualShaderNodeInput> p_input, bool type_changed = next_input_type != prev_input_type; - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_undo_redo(); undo_redo->create_action(TTR("Visual Shader Input Type Changed")); undo_redo->add_do_method(p_input.ptr(), "set_input_name", p_name); @@ -4102,7 +4133,7 @@ void VisualShaderEditor::_uniform_select_item(Ref<VisualShaderNodeUniformRef> p_ bool type_changed = p_uniform_ref->get_uniform_type_by_name(p_name) != p_uniform_ref->get_uniform_type_by_name(prev_name); - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_undo_redo(); undo_redo->create_action(TTR("UniformRef Name Changed")); undo_redo->add_do_method(p_uniform_ref.ptr(), "set_uniform_name", p_name); @@ -4146,7 +4177,7 @@ void VisualShaderEditor::_varying_select_item(Ref<VisualShaderNodeVarying> p_var bool is_getter = Ref<VisualShaderNodeVaryingGetter>(p_varying.ptr()).is_valid(); - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_undo_redo(); undo_redo->create_action(TTR("Varying Name Changed")); undo_redo->add_do_method(p_varying.ptr(), "set_varying_name", p_name); @@ -4281,6 +4312,9 @@ void VisualShaderEditor::_update_varying_tree() { 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_VECTOR_2D: item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons"))); break; @@ -4290,8 +4324,8 @@ void VisualShaderEditor::_update_varying_tree() { 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_COLOR: - item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Color"), SNAME("EditorIcons"))); + 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"))); @@ -4400,7 +4434,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(); @@ -4563,9 +4597,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 (ShaderLanguage::DataType)RS::global_variable_type_get_shader_datatype(gvt); +static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) { + RS::GlobalShaderUniformType gvt = RS::get_singleton()->global_shader_uniform_get_type(p_variable); + return (ShaderLanguage::DataType)RS::global_shader_uniform_type_get_shader_datatype(gvt); } void VisualShaderEditor::_update_preview() { @@ -4582,7 +4616,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; @@ -4642,20 +4676,20 @@ void VisualShaderEditor::_bind_methods() { ClassDB::bind_method("_is_available", &VisualShaderEditor::_is_available); } -VisualShaderEditor *VisualShaderEditor::singleton = nullptr; - VisualShaderEditor::VisualShaderEditor() { - singleton = this; ShaderLanguage::get_keyword_list(&keyword_list); 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"); graph->set_minimap_opacity(graph_minimap_opacity); + float graph_lines_curvature = EditorSettings::get_singleton()->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_BOOLEAN); @@ -4666,13 +4700,13 @@ VisualShaderEditor::VisualShaderEditor() { 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)); @@ -4775,7 +4809,7 @@ VisualShaderEditor::VisualShaderEditor() { 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(Button); varying_button->set_flat(true); @@ -4801,7 +4835,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)); @@ -4825,7 +4859,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 @@ -4906,7 +4940,7 @@ 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)); @@ -4917,7 +4951,7 @@ VisualShaderEditor::VisualShaderEditor() { add_varying_dialog = memnew(ConfirmationDialog); add_varying_dialog->set_title(TTR("Create Shader Varying")); add_varying_dialog->set_exclusive(false); - add_varying_dialog->get_ok_button()->set_text(TTR("Create")); + 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); @@ -4932,10 +4966,11 @@ VisualShaderEditor::VisualShaderEditor() { varying_type = memnew(OptionButton); hb->add_child(varying_type); varying_type->add_item("Float"); + varying_type->add_item("Int"); varying_type->add_item("Vector2"); varying_type->add_item("Vector3"); varying_type->add_item("Vector4"); - varying_type->add_item("Color"); + varying_type->add_item("Boolean"); varying_type->add_item("Transform"); varying_name = memnew(LineEdit); @@ -4961,7 +4996,7 @@ VisualShaderEditor::VisualShaderEditor() { remove_varying_dialog = memnew(ConfirmationDialog); remove_varying_dialog->set_title(TTR("Delete Shader Varying")); remove_varying_dialog->set_exclusive(false); - remove_varying_dialog->get_ok_button()->set_text(TTR("Delete")); + 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); @@ -4982,7 +5017,7 @@ VisualShaderEditor::VisualShaderEditor() { } 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); @@ -5027,8 +5062,8 @@ VisualShaderEditor::VisualShaderEditor() { 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", "VisualShaderNodeVectorFunc", TTR("Converts HSV vector to RGB equivalent."), { VisualShaderNodeVectorFunc::FUNC_HSV2RGB, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); - add_options.push_back(AddOption("RGB2HSV", "Color", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts RGB vector to HSV equivalent."), { VisualShaderNodeVectorFunc::FUNC_RGB2HSV, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, 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("Burn", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Burn operator."), { VisualShaderNodeColorOp::OP_BURN }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); @@ -5041,8 +5076,8 @@ VisualShaderEditor::VisualShaderEditor() { 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("ColorConstant", "Color", "Variables", "VisualShaderNodeColorConstant", TTR("Color constant."))); - add_options.push_back(AddOption("ColorUniform", "Color", "Variables", "VisualShaderNodeColorUniform", TTR("Color uniform."))); + add_options.push_back(AddOption("ColorConstant", "Color", "Variables", "VisualShaderNodeColorConstant", TTR("Color constant."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ColorUniform", "Color", "Variables", "VisualShaderNodeColorUniform", TTR("Color uniform."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); // COMMON @@ -5147,6 +5182,10 @@ VisualShaderEditor::VisualShaderEditor() { 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("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("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)); @@ -5162,6 +5201,10 @@ VisualShaderEditor::VisualShaderEditor() { 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_FRAGMENT, 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_FRAGMENT, 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_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("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)); @@ -5238,6 +5281,8 @@ VisualShaderEditor::VisualShaderEditor() { 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)); @@ -5304,7 +5349,7 @@ VisualShaderEditor::VisualShaderEditor() { 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("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)); @@ -5368,25 +5413,25 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("UVFunc", "Textures", "Common", "VisualShaderNodeUVFunc", TTR("Function to be 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."))); + 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."))); + 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."))); + add_options.push_back(AddOption("CurveXYZTexture", "Textures", "Functions", "VisualShaderNodeCurveXYZTexture", TTR("Perform the three components curve texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); texture2d_node_option_idx = add_options.size(); - add_options.push_back(AddOption("Texture2D", "Textures", "Functions", "VisualShaderNodeTexture", TTR("Perform the 2D texture lookup."))); + 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."))); + 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."))); + 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."))); - add_options.push_back(AddOption("TextureUniform", "Textures", "Variables", "VisualShaderNodeTextureUniform", TTR("2D texture uniform lookup."))); + add_options.push_back(AddOption("CubeMapUniform", "Textures", "Variables", "VisualShaderNodeCubemapUniform", TTR("Cubic texture uniform lookup."), {}, VisualShaderNode::PORT_TYPE_SAMPLER)); + add_options.push_back(AddOption("TextureUniform", "Textures", "Variables", "VisualShaderNodeTextureUniform", TTR("2D texture uniform lookup."), {}, VisualShaderNode::PORT_TYPE_SAMPLER)); add_options.push_back(AddOption("TextureUniformTriplanar", "Textures", "Variables", "VisualShaderNodeTextureUniformTriplanar", TTR("2D texture uniform lookup with triplanar."), {}, -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."))); - add_options.push_back(AddOption("Texture3DUniform", "Textures", "Variables", "VisualShaderNodeTexture3DUniform", TTR("3D texture uniform lookup."))); + add_options.push_back(AddOption("Texture2DArrayUniform", "Textures", "Variables", "VisualShaderNodeTexture2DArrayUniform", TTR("2D array of textures uniform lookup."), {}, VisualShaderNode::PORT_TYPE_SAMPLER)); + add_options.push_back(AddOption("Texture3DUniform", "Textures", "Variables", "VisualShaderNodeTexture3DUniform", TTR("3D texture uniform lookup."), {}, VisualShaderNode::PORT_TYPE_SAMPLER)); // TRANSFORM @@ -5488,16 +5533,16 @@ VisualShaderEditor::VisualShaderEditor() { 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_FRAC, 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_FRAC, 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_FRAC, 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("Length", "Vector", "Functions", "VisualShaderNodeVectorLen", TTR("Calculates the length of a vector."), { VisualShaderNodeVectorLen::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Length", "Vector", "Functions", "VisualShaderNodeVectorLen", TTR("Calculates the length of a vector."), { VisualShaderNodeVectorLen::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Length", "Vector", "Functions", "VisualShaderNodeVectorLen", TTR("Calculates the length of a vector."), { VisualShaderNodeVectorLen::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_SCALAR)); + 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)); @@ -5537,8 +5582,12 @@ VisualShaderEditor::VisualShaderEditor() { 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)); @@ -5624,65 +5673,27 @@ VisualShaderEditor::VisualShaderEditor() { _update_options_menu(); - undo_redo = EditorNode::get_singleton()->get_undo_redo(); + undo_redo = EditorNode::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->connect("variant_changed", callable_mp(this, &VisualShaderEditor::_port_edited)); -} - -///////////////// - -void VisualShaderEditorPlugin::edit(Object *p_object) { - visual_shader_editor->edit(Object::cast_to<VisualShader>(p_object)); -} - -bool VisualShaderEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("VisualShader"); -} - -void VisualShaderEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - //editor->hide_animation_player_editors(); - //editor->animation_panel_make_visible(true); - button->show(); - EditorNode::get_singleton()->make_bottom_panel_item_visible(visual_shader_editor); - visual_shader_editor->update_nodes(); - visual_shader_editor->set_process_input(true); - //visual_shader_editor->set_process(true); - } else { - if (visual_shader_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->hide_bottom_panel(); - } - button->hide(); - visual_shader_editor->set_process_input(false); - //visual_shader_editor->set_process(false); - } -} - -VisualShaderEditorPlugin::VisualShaderEditorPlugin() { - visual_shader_editor = memnew(VisualShaderEditor); - visual_shader_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE); - - button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("VisualShader"), visual_shader_editor); - button->hide(); -} + property_editor_popup = memnew(PopupPanel); + property_editor_popup->set_min_size(Size2i(180, 0) * EDSCALE); + add_child(property_editor_popup); -VisualShaderEditorPlugin::~VisualShaderEditorPlugin() { + edited_property_holder.instantiate(); } -//////////////// - class VisualShaderNodePluginInputEditor : public OptionButton { GDCLASS(VisualShaderNodePluginInputEditor, OptionButton); + VisualShaderEditor *editor = nullptr; Ref<VisualShaderNodeInput> input; public: @@ -5695,13 +5706,11 @@ public: } void _item_selected(int p_item) { - VisualShaderEditor *editor = VisualShaderEditor::get_singleton(); - if (editor) { - editor->call_deferred(SNAME("_input_select_item"), input, get_item_text(p_item)); - } + editor->call_deferred(SNAME("_input_select_item"), input, get_item_text(p_item)); } - void setup(const Ref<VisualShaderNodeInput> &p_input) { + 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")), @@ -5734,6 +5743,7 @@ public: class VisualShaderNodePluginVaryingEditor : public OptionButton { GDCLASS(VisualShaderNodePluginVaryingEditor, OptionButton); + VisualShaderEditor *editor = nullptr; Ref<VisualShaderNodeVarying> varying; public: @@ -5744,21 +5754,20 @@ public: } void _item_selected(int p_item) { - VisualShaderEditor *editor = VisualShaderEditor::get_singleton(); - if (editor) { - editor->call_deferred(SNAME("_varying_select_item"), varying, get_item_text(p_item)); - } + editor->call_deferred(SNAME("_varying_select_item"), varying, get_item_text(p_item)); } - void setup(const Ref<VisualShaderNodeVarying> &p_varying, VisualShader::Type p_type) { + 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("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("Color"), 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")), }; @@ -5811,6 +5820,7 @@ public: class VisualShaderNodePluginUniformRefEditor : public OptionButton { GDCLASS(VisualShaderNodePluginUniformRefEditor, OptionButton); + VisualShaderEditor *editor = nullptr; Ref<VisualShaderNodeUniformRef> uniform_ref; public: @@ -5823,13 +5833,11 @@ public: } void _item_selected(int p_item) { - VisualShaderEditor *editor = VisualShaderEditor::get_singleton(); - if (editor) { - editor->call_deferred(SNAME("_uniform_select_item"), uniform_ref, get_item_text(p_item)); - } + editor->call_deferred(SNAME("_uniform_select_item"), uniform_ref, get_item_text(p_item)); } - void setup(const Ref<VisualShaderNodeUniformRef> &p_uniform_ref) { + void setup(VisualShaderEditor *p_editor, const Ref<VisualShaderNodeUniformRef> &p_uniform_ref) { + editor = p_editor; uniform_ref = p_uniform_ref; Ref<Texture2D> type_icon[] = { @@ -5863,8 +5871,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: @@ -5873,7 +5882,7 @@ public: return; } - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_undo_redo(); updating = true; undo_redo->create_action(TTR("Edit Visual Property:") + " " + p_property, UndoRedo::MERGE_ENDS); @@ -5881,28 +5890,25 @@ 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") { - VisualShaderEditor *editor = VisualShaderEditor::get_singleton(); - if (editor) { - VisualShaderGraphPlugin *graph_plugin = editor->get_graph_plugin(); - if (graph_plugin) { - 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); - } + VisualShaderGraphPlugin *graph_plugin = editor->get_graph_plugin(); + if (graph_plugin) { + 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(); @@ -5919,15 +5925,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) { + 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; @@ -5938,7 +5944,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; @@ -5991,19 +5998,19 @@ Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_par if (p_shader.is_valid() && (p_node->is_class("VisualShaderNodeVaryingGetter") || p_node->is_class("VisualShaderNodeVaryingSetter"))) { VisualShaderNodePluginVaryingEditor *editor = memnew(VisualShaderNodePluginVaryingEditor); - editor->setup(p_node, p_shader->get_shader_type()); + editor->setup(vseditor, p_node, p_shader->get_shader_type()); return editor; } if (p_node->is_class("VisualShaderNodeUniformRef")) { VisualShaderNodePluginUniformRefEditor *editor = memnew(VisualShaderNodePluginUniformRefEditor); - editor->setup(p_node); + editor->setup(vseditor, p_node); return editor; } if (p_node->is_class("VisualShaderNodeInput")) { VisualShaderNodePluginInputEditor *editor = memnew(VisualShaderNodePluginInputEditor); - editor->setup(p_node); + editor->setup(vseditor, p_node); return editor; } @@ -6058,26 +6065,26 @@ 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) { - VisualShaderEditor *editor = VisualShaderEditor::get_singleton(); - if (!editor) { +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; } - //will not use this, instead will do all the logic setting manually - //emit_signal(SNAME("property_changed"), get_edited_property(), p_which); - - Ref<VisualShader> visual_shader(Object::cast_to<VisualShader>(get_edited_object())); - - if (visual_shader->get_mode() == p_which) { + 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; } - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_undo_redo(); undo_redo->create_action(TTR("Visual Shader Mode Changed")); //do is easy undo_redo->add_do_method(visual_shader.ptr(), "set_mode", p_which); @@ -6148,42 +6155,42 @@ void EditorPropertyShaderMode::_option_selected(int p_which) { 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 uint32_t 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; } @@ -6231,7 +6238,7 @@ 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)); } @@ -6252,7 +6259,8 @@ void VisualShaderNodePortPreview::setup(const Ref<VisualShader> &p_shader, Visua } Size2 VisualShaderNodePortPreview::get_minimum_size() const { - return Size2(100, 100) * EDSCALE; + int port_preview_size = EditorSettings::get_singleton()->get("editors/visual_editors/visual_shader/port_preview_size"); + return Size2(port_preview_size, port_preview_size) * EDSCALE; } void VisualShaderNodePortPreview::_notification(int p_what) { diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index 8db2cf07f9..ede6513b83 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -32,25 +32,37 @@ #define VISUAL_SHADER_EDITOR_PLUGIN_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/code_edit.h" -#include "scene/gui/graph_edit.h" -#include "scene/gui/popup.h" -#include "scene/gui/rich_text_label.h" -#include "scene/gui/tree.h" +#include "editor/plugins/editor_resource_conversion_plugin.h" #include "scene/resources/visual_shader.h" +class Button; +class CodeEdit; +class CodeHighlighter; +class CurveEditor; +class GraphEdit; +class GraphNode; +class PopupMenu; +class PopupPanel; +class RichTextLabel; +class TextEdit; +class Tree; + +class VisualShaderEditor; +class EditorUndoRedoManager; + 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); }; @@ -58,6 +70,8 @@ class VisualShaderGraphPlugin : public RefCounted { GDCLASS(VisualShaderGraphPlugin, RefCounted); private: + VisualShaderEditor *editor = nullptr; + struct InputPort { Button *default_input_button = nullptr; }; @@ -72,8 +86,8 @@ 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; CodeEdit *expression_edit = nullptr; @@ -81,7 +95,7 @@ private: }; Ref<VisualShader> visual_shader; - Map<int, Link> links; + HashMap<int, Link> links; List<VisualShader::Connection> connections; bool dirty = false; @@ -91,6 +105,7 @@ protected: static void _bind_methods(); public: + void set_editor(VisualShaderEditor *p_editor); void register_shader(VisualShader *p_visual_shader); 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); @@ -128,13 +143,31 @@ public: ~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 = nullptr; + 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 = nullptr; @@ -160,7 +193,7 @@ class VisualShaderEditor : public VBoxContainer { PanelContainer *error_panel = nullptr; Label *error_label = nullptr; - UndoRedo *undo_redo = nullptr; + Ref<EditorUndoRedoManager> undo_redo; Point2 saved_node_pos; bool saved_node_pos_dirty = false; @@ -324,8 +357,6 @@ class VisualShaderEditor : public VBoxContainer { void _update_preview(); String _get_description(int p_idx); - static VisualShaderEditor *singleton; - struct DragOp { VisualShader::Type type = VisualShader::Type::TYPE_MAX; int node = 0; @@ -346,20 +377,20 @@ 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 = -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_uniforms; int selected_comment = -1; int selected_float_constant = -1; @@ -403,9 +434,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); @@ -468,7 +499,7 @@ 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_uniform_refs(HashSet<String> &p_names); void _update_varyings(); void _visibility_changed(); @@ -482,7 +513,6 @@ public: 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(); @@ -493,23 +523,6 @@ public: VisualShaderEditor(); }; -class VisualShaderEditorPlugin : public EditorPlugin { - GDCLASS(VisualShaderEditorPlugin, EditorPlugin); - - VisualShaderEditor *visual_shader_editor = nullptr; - Button *button = nullptr; - -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(); - ~VisualShaderEditorPlugin(); -}; - class VisualShaderNodePluginDefault : public VisualShaderNodePlugin { GDCLASS(VisualShaderNodePluginDefault, VisualShaderNodePlugin); @@ -517,8 +530,8 @@ public: virtual Control *create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node) override; }; -class EditorPropertyShaderMode : public EditorProperty { - GDCLASS(EditorPropertyShaderMode, EditorProperty); +class EditorPropertyVisualShaderMode : public EditorProperty { + GDCLASS(EditorPropertyVisualShaderMode, EditorProperty); OptionButton *options = nullptr; void _option_selected(int p_which); @@ -530,11 +543,11 @@ 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; diff --git a/editor/plugins/voxel_gi_editor_plugin.cpp b/editor/plugins/voxel_gi_editor_plugin.cpp index 6fc6c1ad39..e3b2be33df 100644 --- a/editor/plugins/voxel_gi_editor_plugin.cpp +++ b/editor/plugins/voxel_gi_editor_plugin.cpp @@ -139,7 +139,7 @@ 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); } } diff --git a/editor/plugins/voxel_gi_editor_plugin.h b/editor/plugins/voxel_gi_editor_plugin.h index 621e98beef..43d6f71e26 100644 --- a/editor/plugins/voxel_gi_editor_plugin.h +++ b/editor/plugins/voxel_gi_editor_plugin.h @@ -28,8 +28,8 @@ /* 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_plugin.h" #include "scene/3d/voxel_gi.h" @@ -71,4 +71,4 @@ public: ~VoxelGIEditorPlugin(); }; -#endif // VOXEL_GIEDITORPLUGIN_H +#endif // VOXEL_GI_EDITOR_PLUGIN_H |